From 648aa3d84cd11439b6457aa81285ea9a5ac202f0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 18 Oct 2021 17:49:55 +0200 Subject: Add new button to open the Marketplace For now this button opens the same Marketplace. I must maintain both Marketplaces side-by-side for the moment though. Contributes to issue CURA-8556. --- resources/qml/MainWindow/MainWindowHeader.qml | 38 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 815ddff732..9112c733f3 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -86,7 +86,6 @@ Item // Shortcut button to quick access the Toolbox Controls2.Button { - id: marketplaceButton text: catalog.i18nc("@action:button", "Marketplace") height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) onClicked: Cura.Actions.browsePackages.trigger() @@ -107,7 +106,7 @@ Item anchors.fill: parent radius: parent.radius color: UM.Theme.getColor("primary_text") - opacity: marketplaceButton.hovered ? 0.2 : 0 + opacity: parent.hovered ? 0.2 : 0 Behavior on opacity { NumberAnimation { duration: 100 } } } } @@ -115,7 +114,7 @@ Item contentItem: Label { id: label - text: marketplaceButton.text + text: parent.text font: UM.Theme.getFont("default") color: UM.Theme.getColor("primary_text") width: contentWidth @@ -125,7 +124,7 @@ Item anchors { - right: applicationSwitcher.left + right: marketplaceButton.left rightMargin: UM.Theme.getSize("default_margin").width verticalCenter: parent.verticalCenter } @@ -150,6 +149,37 @@ Item } } + Controls2.Button + { + id: marketplaceButton + width: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) + height: width + anchors + { + verticalCenter: parent.verticalCenter + right: applicationSwitcher.left + rightMargin: UM.Theme.getSize("default_margin").width + } + + background: UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary_text") + source: UM.Theme.getIcon("Shop") + + Rectangle + { + anchors.fill: parent + radius: UM.Theme.getSize("action_button_radius").width + color: UM.Theme.getColor("primary_text") + opacity: marketplaceButton.hovered ? 0.2 : 0 + Behavior on opacity { NumberAnimation { duration: 100; } } + } + } + + onClicked: Cura.Actions.browsePackages.trigger() + } + ApplicationSwitcher { id: applicationSwitcher -- cgit v1.2.3 From 73ad2a4e08f66ae862cb52de1707880426cb2050 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 13:06:04 +0200 Subject: Add Marketplace plug-in, starting a rewrite from the Toolbox This plug-in will be a complete re-write of the previous Toolbox plug-in. It's intended to solve some of the inherent architectural problems with the Toolbox. We're calling it Marketplace now as well. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 10 ++++++++++ plugins/Marketplace/__init__.py | 17 +++++++++++++++++ plugins/Marketplace/plugin.json | 8 ++++++++ 3 files changed, 35 insertions(+) create mode 100644 plugins/Marketplace/Marketplace.py create mode 100644 plugins/Marketplace/__init__.py create mode 100644 plugins/Marketplace/plugin.json diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py new file mode 100644 index 0000000000..a113e46597 --- /dev/null +++ b/plugins/Marketplace/Marketplace.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.PluginObject import PluginObject + +class Marketplace(PluginObject): + """ + The main managing object for the Marketplace plug-in. + """ + pass # TODO \ No newline at end of file diff --git a/plugins/Marketplace/__init__.py b/plugins/Marketplace/__init__.py new file mode 100644 index 0000000000..bd65062ba6 --- /dev/null +++ b/plugins/Marketplace/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from .Marketplace import Marketplace + +def getMetaData(): + """ + Extension-type plug-ins don't have any specific metadata being used by Cura. + """ + return {} + + +def register(app): + """ + Register the plug-in object with Uranium. + """ + return { "extension": Marketplace() } diff --git a/plugins/Marketplace/plugin.json b/plugins/Marketplace/plugin.json new file mode 100644 index 0000000000..7eeeb5c986 --- /dev/null +++ b/plugins/Marketplace/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Marketplace", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "api": 7, + "description": "Manages extensions to the application and allows browsing extensions from the Ultimaker website.", + "i18n-catalog": "cura" +} -- cgit v1.2.3 From aa4b7ddb8bce12c4ff2ea8864c1dad63a7a0a7c4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 13:09:22 +0200 Subject: Revert "Add new button to open the Marketplace" This reverts commit 648aa3d84cd11439b6457aa81285ea9a5ac202f0. Apparently we don't want to retain the old Toolbox alongside, not even during development. Contributes to issue CURA-8556. --- resources/qml/MainWindow/MainWindowHeader.qml | 38 +++------------------------ 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 9112c733f3..815ddff732 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -86,6 +86,7 @@ Item // Shortcut button to quick access the Toolbox Controls2.Button { + id: marketplaceButton text: catalog.i18nc("@action:button", "Marketplace") height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) onClicked: Cura.Actions.browsePackages.trigger() @@ -106,7 +107,7 @@ Item anchors.fill: parent radius: parent.radius color: UM.Theme.getColor("primary_text") - opacity: parent.hovered ? 0.2 : 0 + opacity: marketplaceButton.hovered ? 0.2 : 0 Behavior on opacity { NumberAnimation { duration: 100 } } } } @@ -114,7 +115,7 @@ Item contentItem: Label { id: label - text: parent.text + text: marketplaceButton.text font: UM.Theme.getFont("default") color: UM.Theme.getColor("primary_text") width: contentWidth @@ -124,7 +125,7 @@ Item anchors { - right: marketplaceButton.left + right: applicationSwitcher.left rightMargin: UM.Theme.getSize("default_margin").width verticalCenter: parent.verticalCenter } @@ -149,37 +150,6 @@ Item } } - Controls2.Button - { - id: marketplaceButton - width: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) - height: width - anchors - { - verticalCenter: parent.verticalCenter - right: applicationSwitcher.left - rightMargin: UM.Theme.getSize("default_margin").width - } - - background: UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon("Shop") - - Rectangle - { - anchors.fill: parent - radius: UM.Theme.getSize("action_button_radius").width - color: UM.Theme.getColor("primary_text") - opacity: marketplaceButton.hovered ? 0.2 : 0 - Behavior on opacity { NumberAnimation { duration: 100; } } - } - } - - onClicked: Cura.Actions.browsePackages.trigger() - } - ApplicationSwitcher { id: applicationSwitcher -- cgit v1.2.3 From 5897b3de3841c4ca6695090234d3c780efc73ec7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 13:43:41 +0200 Subject: Add function to open Marketplace window This will now load in a QML file, cache it, and create an empty window with the title 'Marketplace'. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 37 +++++++++++++++++++++-- plugins/Marketplace/resources/qml/Marketplace.qml | 21 +++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/Marketplace.qml diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index a113e46597..5fd976cbfc 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -1,10 +1,41 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.PluginObject import PluginObject +import os.path +from PyQt5.QtCore import pyqtSlot +from typing import Optional, TYPE_CHECKING -class Marketplace(PluginObject): +from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. +from UM.Extension import Extension # We are implementing the main object of an extension here. +from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). + +if TYPE_CHECKING: + from PyQt5.QtCore import QObject + +class Marketplace(Extension): """ The main managing object for the Marketplace plug-in. """ - pass # TODO \ No newline at end of file + + def __init__(self): + super().__init__() + self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. + + @pyqtSlot() + def show(self) -> None: + """ + Opens the window of the Marketplace. + + If the window hadn't been loaded yet into Qt, it will be created lazily. + """ + if self._window is None: + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") + self._window = CuraApplication.getInstance().createQmlComponent(path, {}) + if self._window is None: # Still None? Failed to load the QML then. + Logger.error(f"Failed to load QML for Marketplace window.") + return + self._window.show() \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml new file mode 100644 index 0000000000..96b13d5522 --- /dev/null +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -0,0 +1,21 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.15 +import QtQuick.Window 2.2 + +import UM 1.2 as UM + +Window +{ + id: marketplaceDialog + property variant catalog: UM.I18nCatalog { name: "cura" } + + minimumWidth: UM.Theme.getSize("modal_window_minimum").width + minimumHeight: UM.Theme.getSize("modal_window_minimum").height + width: minimumWidth + height: minimumHeight + + title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. + modality: Qt.NonModal +} \ No newline at end of file -- cgit v1.2.3 From c35b1f41352541f59b5ef54873b3c8802dc843e3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 13:45:25 +0200 Subject: Bring window into focus when it opens It can be behind the Cura main window now, where the user won't see it if it was already open. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 5fd976cbfc..be2f66c7b2 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -38,4 +38,5 @@ class Marketplace(Extension): if self._window is None: # Still None? Failed to load the QML then. Logger.error(f"Failed to load QML for Marketplace window.") return - self._window.show() \ No newline at end of file + self._window.show() + self._window.requestActivate() # Bring window into focus, if it was already open in the background. -- cgit v1.2.3 From 8beac74417295e6ca1bb539ea07310eaac0ed3df Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 13:46:21 +0200 Subject: Let Marketplace button open Marketplace instead of Toolbox This hijacks the button to open something else. The old Toolbox is no longer accessible now. Contributes to issue CURA-8556. --- resources/qml/MainWindow/ApplicationMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index 62b3a71ee8..b7a016fabc 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -202,7 +202,7 @@ Item target: Cura.Actions.browsePackages function onTriggered() { - curaExtensions.callExtensionMethod("Toolbox", "launch") + curaExtensions.callExtensionMethod("Marketplace", "show") } } -- cgit v1.2.3 From 97edf59660794cd5de67e9881bc0fd71e3163baa Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 16:50:57 +0200 Subject: High-level layout of Marketplace window Currently just a title and a page. The title is separate because in between there will be the tabs selecting which page is shown. The title will also change depending on that, but that'll have to be implemented separately. The page is loaded with a loader to make it efficient, and also to make it extensible when the tabs get implemented in a follow-up ticket. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Marketplace.qml | 33 +++++++++++++++++++++++ plugins/Marketplace/resources/qml/Plugins.qml | 9 +++++++ 2 files changed, 42 insertions(+) create mode 100644 plugins/Marketplace/resources/qml/Plugins.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 96b13d5522..b0b3d904e5 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -2,6 +2,8 @@ // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import QtQuick.Window 2.2 import UM 1.2 as UM @@ -18,4 +20,35 @@ Window title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. modality: Qt.NonModal + + Rectangle //Background color. + { + anchors.fill: parent + color: UM.Theme.getColor("main_background") + + ColumnLayout + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + spacing: UM.Theme.getSize("default_margin").height + + Label //Page title. + { + Layout.preferredWidth: parent.width + Layout.preferredHeight: contentHeight + + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + text: catalog.i18nc("@header", "Install Plugins") + } + Loader //Page contents. + { + Layout.preferredWidth: parent.width + Layout.fillHeight: true + + source: "Plugins.qml" + } + } + } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml new file mode 100644 index 0000000000..2d37483510 --- /dev/null +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -0,0 +1,9 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.15 + +Rectangle +{ + color: "pink" //TODO +} \ No newline at end of file -- cgit v1.2.3 From ffce865c855ffd79efea1250f1a58c1c1eebde5f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 17:47:40 +0200 Subject: Add constant for API URLs I figured this out now. Don't want to forget it. We'll need it later anyway. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index be2f66c7b2..1ae19718ef 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -5,7 +5,9 @@ import os.path from PyQt5.QtCore import pyqtSlot from typing import Optional, TYPE_CHECKING +from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. +from cura.UltimakerCloud import UltimakerCloudConstants from UM.Extension import Extension # We are implementing the main object of an extension here. from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). @@ -18,6 +20,9 @@ class Marketplace(Extension): The main managing object for the Marketplace plug-in. """ + ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. + PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. + def __init__(self): super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. -- cgit v1.2.3 From cf2b0d27778b76ad2dfc90a8a19305709fe431c9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Oct 2021 17:48:21 +0200 Subject: Add empty ListModel to store list of packages with This model does nothing yet. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 plugins/Marketplace/PackageList.py diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py new file mode 100644 index 0000000000..62cac7228e --- /dev/null +++ b/plugins/Marketplace/PackageList.py @@ -0,0 +1,22 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import Qt + +from UM.Qt.ListModel import ListModel + +class PackageList(ListModel): + """ + Represents a list of packages to be displayed in the interface. + + The list can be filtered (e.g. on package type, materials vs. plug-ins) and + paginated. + """ + + PackageIDRole = Qt.UserRole + 1 + DisplayNameRole = Qt.UserRole + 2 + # TODO: Add more roles here when we need to display more information about packages. + + def _update(self) -> None: + # TODO: Get list of packages from Marketplace class. + pass -- cgit v1.2.3 From 0f5c923d935e7df7112f54054926a5efde06d8f7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 15:03:41 +0200 Subject: Add model to represent packages and export information to QML We'll construct a bunch of these when we receive information from the API. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 16 ++++++++++++---- plugins/Marketplace/PackageModel.py | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 plugins/Marketplace/PackageModel.py diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 62cac7228e..8a70096802 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -2,9 +2,14 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import Qt - +from typing import List, TYPE_CHECKING from UM.Qt.ListModel import ListModel +from .PackageModel import PackageModel # This list is a list of PackageModels. + +if TYPE_CHECKING: + from PyQt5.QtCore import QObject + class PackageList(ListModel): """ Represents a list of packages to be displayed in the interface. @@ -13,9 +18,12 @@ class PackageList(ListModel): paginated. """ - PackageIDRole = Qt.UserRole + 1 - DisplayNameRole = Qt.UserRole + 2 - # TODO: Add more roles here when we need to display more information about packages. + PackageRole = Qt.UserRole + 1 + + def __init__(self, parent: "QObject" = None): + super().__init__(parent) + + self._packages: List[PackageModel] = [] def _update(self) -> None: # TODO: Get list of packages from Marketplace class. diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py new file mode 100644 index 0000000000..340a26c451 --- /dev/null +++ b/plugins/Marketplace/PackageModel.py @@ -0,0 +1,25 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import pyqtProperty, QObject + +class PackageModel(QObject): + """ + Represents a package, containing all the relevant information to be displayed about a package. + + Effectively this behaves like a glorified named tuple, but as a QObject so that its properties can be obtained from + QML. + """ + + def __init__(self, package_id: str, display_name: str, parent: QObject = None): + super().__init__(parent) + self._package_id = package_id + self._display_name = display_name + + @pyqtProperty(str, constant = True) + def packageId(self) -> str: + return self._package_id + + @pyqtProperty(str, constant = True) + def displayName(self) -> str: + return self._display_name -- cgit v1.2.3 From 5851ad52c69767560138972bc599185a3f185316 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 15:15:56 +0200 Subject: Add property to tell if the list is currently loading or loading more We'll need to display a spinner of some kind in the front-end, I think. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 8a70096802..df663a4bd4 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,7 +1,7 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt from typing import List, TYPE_CHECKING from UM.Qt.ListModel import ListModel @@ -20,10 +20,39 @@ class PackageList(ListModel): PackageRole = Qt.UserRole + 1 + ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. + def __init__(self, parent: "QObject" = None): super().__init__(parent) self._packages: List[PackageModel] = [] + self.setIsLoading(True) + + self.requestFirst() + + def requestFirst(self) -> None: + """ + Make a request for the first paginated page of packages. + + When the request is done, the list will get updated with the new package models. + """ + self.setIsLoading(True) + + isLoadingChanged = pyqtSignal() + + @pyqtSlot(bool) + 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) + 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 def _update(self) -> None: # TODO: Get list of packages from Marketplace class. -- cgit v1.2.3 From 4337e81b77becb4ca20ad9bbcd17492bbad4da6d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 15:33:37 +0200 Subject: Make request to Marketplace API when package list loads We don't parse the response just yet, but this is part of the work. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 6 +++--- plugins/Marketplace/PackageList.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 1ae19718ef..921e9d1290 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -15,14 +15,14 @@ from UM.PluginRegistry import PluginRegistry # To find out where we are stored if TYPE_CHECKING: from PyQt5.QtCore import QObject +ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. +PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. + class Marketplace(Extension): """ The main managing object for the Marketplace plug-in. """ - ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. - PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. - def __init__(self): super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index df663a4bd4..dfb0f0ad57 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,14 +1,20 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import List, TYPE_CHECKING +from typing import List, Optional, TYPE_CHECKING from UM.Qt.ListModel import ListModel +from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. +from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. +from .Marketplace import PACKAGES_URL # To get the list of packages. from .PackageModel import PackageModel # This list is a list of PackageModels. if TYPE_CHECKING: from PyQt5.QtCore import QObject + from PyQt5.QtNetwork import QNetworkReply class PackageList(ListModel): """ @@ -26,7 +32,8 @@ class PackageList(ListModel): super().__init__(parent) self._packages: List[PackageModel] = [] - self.setIsLoading(True) + self._is_loading = True + self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self.requestFirst() @@ -38,6 +45,14 @@ class PackageList(ListModel): """ self.setIsLoading(True) + http = HttpRequestManager.getInstance() + http.get( + PACKAGES_URL, + scope = self._scope, + callback = self._parseResponse, + error_callback = self._onError + ) + isLoadingChanged = pyqtSignal() @pyqtSlot(bool) @@ -54,6 +69,23 @@ class PackageList(ListModel): """ return self._is_loading + def _parseResponse(self, reply: "QNetworkReply") -> None: + """ + Parse the response from the package list API request. + + This converts that response into PackageModels, and triggers the ListModel to update. + :param reply: A reply containing information about a number of packages. + """ + pass # TODO: Parse reply dictionary. + + 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. + """ + pass # TODO: Handle errors. + def _update(self) -> None: # TODO: Get list of packages from Marketplace class. pass -- cgit v1.2.3 From 3138452f94c99d2fcec5f6fc6ad01fc50fb202ec Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 15:46:46 +0200 Subject: Allow PackageList to be used as a model from QML QML is leading here and holding the pointers for creation and destruction. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 5 +++++ plugins/Marketplace/PackageList.py | 4 ++-- plugins/Marketplace/resources/qml/Plugins.qml | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 921e9d1290..506b8347e2 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -3,6 +3,7 @@ import os.path from PyQt5.QtCore import pyqtSlot +from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING from cura.ApplicationMetadata import CuraSDKVersion @@ -12,6 +13,8 @@ from UM.Extension import Extension # We are implementing the main object of an from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). +from .PackageList import PackageList # To register this type with QML. + if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -27,6 +30,8 @@ class Marketplace(Extension): super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. + qmlRegisterType(PackageList, "Cura", 1, 7, "PackageList") + @pyqtSlot() def show(self) -> None: """ diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index dfb0f0ad57..e6a4e7a560 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -9,7 +9,7 @@ from UM.Qt.ListModel import ListModel from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. -from .Marketplace import PACKAGES_URL # To get the list of packages. +from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. from .PackageModel import PackageModel # This list is a list of PackageModels. if TYPE_CHECKING: @@ -47,7 +47,7 @@ class PackageList(ListModel): http = HttpRequestManager.getInstance() http.get( - PACKAGES_URL, + Marketplace.PACKAGES_URL, scope = self._scope, callback = self._parseResponse, error_callback = self._onError diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 2d37483510..a43a9e55a4 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -2,8 +2,18 @@ // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.15 +import QtQuick.Controls 2.15 +import Cura 1.7 as Cura -Rectangle +Item { - color: "pink" //TODO + Repeater + { + model: Cura.PackageList{} + + Label + { + text: "Test" //TODO: Create a card for each package. + } + } } \ No newline at end of file -- cgit v1.2.3 From 6415a2649e3010cd6b967ecc1f7d1052e87e81e1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 16:02:46 +0200 Subject: Parse responses from package API call Only positive responses so far. Error handling is not implemented yet. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 9 ++++++++- plugins/Marketplace/PackageModel.py | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index e6a4e7a560..5155559dc6 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -76,7 +76,14 @@ class PackageList(ListModel): This converts that response into PackageModels, and triggers the ListModel to update. :param reply: A reply containing information about a number of packages. """ - pass # TODO: Parse reply dictionary. + response_data = HttpRequestManager.readJSON(reply) + if "data" not in response_data: + return # TODO: Handle invalid response. + + for package_data in response_data["data"]: + package = PackageModel(package_data, parent = self) + self._packages.append(package) + self._update() def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: """ diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 340a26c451..eba9d63a6c 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -2,19 +2,29 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, QObject +from typing import Any, Dict + +from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. + +catalog = i18nCatalog("cura") class PackageModel(QObject): """ Represents a package, containing all the relevant information to be displayed about a package. Effectively this behaves like a glorified named tuple, but as a QObject so that its properties can be obtained from - QML. + QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_id: str, display_name: str, parent: QObject = None): + def __init__(self, package_data: Dict[str, Any], parent: QObject = None): + """ + Constructs a new model for a single package. + :param package_data: The data received from the Marketplace API about the package to create. + :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). + """ super().__init__(parent) - self._package_id = package_id - self._display_name = display_name + self._package_id = package_data.get("package_id", "UnknownPackageId") + self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) @pyqtProperty(str, constant = True) def packageId(self) -> str: -- cgit v1.2.3 From 38038b37527d63a7f1da04aabfae23c50509c055 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 16:10:23 +0200 Subject: Store items directly in listModel when parsing them No need to use a custom list in Python and update the ListModel from that. This is much simpler and more efficient. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 10 +++------- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 5155559dc6..37755498f8 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -31,10 +31,11 @@ class PackageList(ListModel): def __init__(self, parent: "QObject" = None): super().__init__(parent) - self._packages: List[PackageModel] = [] self._is_loading = True self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + self.addRoleName(self.PackageRole, "package") + self.requestFirst() def requestFirst(self) -> None: @@ -82,8 +83,7 @@ class PackageList(ListModel): for package_data in response_data["data"]: package = PackageModel(package_data, parent = self) - self._packages.append(package) - self._update() + self.appendItem({"package": package}) # Add it to this list model. def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: """ @@ -92,7 +92,3 @@ class PackageList(ListModel): :param error: The error status of the request. """ pass # TODO: Handle errors. - - def _update(self) -> None: - # TODO: Get list of packages from Marketplace class. - pass diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index a43a9e55a4..037843600b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Cura 1.7 as Cura -Item +Column { Repeater { -- cgit v1.2.3 From b585c02207fdedc9afc7622f1bb1cdfcb278c76e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 16:35:54 +0200 Subject: Add background to page content, and restructure margins To display the background across the entire bottom side and not with the margins of the column, we have to restructure where the margins are a bit. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Marketplace.qml | 34 +++++++++++++++++------ resources/themes/cura-light/theme.json | 1 + 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index b0b3d904e5..2b17d3ebf3 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -29,25 +29,43 @@ Window ColumnLayout { anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").height - Label //Page title. + Item //Page title. { Layout.preferredWidth: parent.width - Layout.preferredHeight: contentHeight + Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - text: catalog.i18nc("@header", "Install Plugins") + Label + { + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + bottom: parent.bottom + } + + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + text: catalog.i18nc("@header", "Install Plugins") + } } - Loader //Page contents. + Rectangle //Page contents. { Layout.preferredWidth: parent.width Layout.fillHeight: true + color: UM.Theme.getColor("detail_background") + + Loader //Page contents. + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width - source: "Plugins.qml" + source: "Plugins.qml" + } } } } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 78676da926..0f7ca004f9 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -173,6 +173,7 @@ "colors": { "main_background": [255, 255, 255, 255], + "detail_background": [243, 243, 243, 255], "wide_lining": [245, 245, 245, 255], "thick_lining": [180, 180, 180, 255], "lining": [192, 193, 194, 255], -- cgit v1.2.3 From 031c8efbe6cb09aa42e01d82d59ff4ef7a9d0b9a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 16:56:53 +0200 Subject: Implement pagination for package list The simplest way I can think of. Currently we only call the request function once, so we can only get the first page. Before calling it multiple times, we should check if there are more pages by checking if the request URL is an empty string. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 37755498f8..b93cc3e083 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -33,12 +33,13 @@ 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.addRoleName(self.PackageRole, "package") - self.requestFirst() + self.request() - def requestFirst(self) -> None: + def request(self) -> None: """ Make a request for the first paginated page of packages. @@ -48,7 +49,7 @@ class PackageList(ListModel): http = HttpRequestManager.getInstance() http.get( - Marketplace.PACKAGES_URL, + self._request_url, scope = self._scope, callback = self._parseResponse, error_callback = self._onError @@ -78,13 +79,15 @@ class PackageList(ListModel): :param reply: A reply containing information about a number of packages. """ response_data = HttpRequestManager.readJSON(reply) - if "data" not in response_data: + if "data" not in response_data or "links" not in response_data: return # TODO: Handle invalid response. for package_data in response_data["data"]: 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. + def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: """ Handles networking and server errors when requesting the list of packages. -- cgit v1.2.3 From 1320d8c9f4cc08d4c8bb31120b0c7fd66a0c32fb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 17:03:53 +0200 Subject: Add a simplistic design for a card for each package It just displays the package name for now. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 35 +++++++++++++++++++++++---- resources/themes/cura-light/theme.json | 1 + 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 037843600b..2667bd1729 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -4,16 +4,41 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Cura 1.7 as Cura +import UM 1.0 as UM -Column +ScrollView { - Repeater + clip: true + + Column { - model: Cura.PackageList{} + id: pluginColumn + width: parent.width + spacing: UM.Theme.getSize("default_margin").height - Label + Repeater { - text: "Test" //TODO: Create a card for each package. + model: Cura.PackageList{} + + 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: (parent.height - height) / 2 + + text: model.package.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + } + } } } } \ No newline at end of file diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 0f7ca004f9..abd4844f47 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -553,6 +553,7 @@ "standard_list_lineheight": [1.5, 1.5], "standard_arrow": [1.0, 1.0], + "card": [25.0, 8.75], "button": [4, 4], "button_icon": [2.5, 2.5], -- cgit v1.2.3 From 27da03d862ba8ff714946065a68da337645e1a94 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 17:10:32 +0200 Subject: Add a property to see whether there are any more packages to load Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index b93cc3e083..07ee103334 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -71,6 +71,17 @@ class PackageList(ListModel): """ return self._is_loading + hasMoreChanged = pyqtSignal() + + @pyqtProperty(bool, notify = hasMoreChanged) + def hasMore(self) -> bool: + """ + Returns whether there are more packages to load. + :return: ``True`` if there are more packages to load, or ``False`` if we've reached the last page of the + pagination. + """ + return self._request_url != "" + def _parseResponse(self, reply: "QNetworkReply") -> None: """ Parse the response from the package list API request. @@ -87,6 +98,7 @@ class PackageList(ListModel): 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() def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: """ -- cgit v1.2.3 From 35ec8f71904e4ffaa293c1dc9ddee691681fd572 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 17:34:14 +0200 Subject: Add basic layout for button to load more packages Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 37 ++++++++++++++++++++++ .../themes/cura-light/icons/default/ArrowDown.svg | 3 ++ 2 files changed, 40 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/ArrowDown.svg diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 2667bd1729..010713eb4b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -40,5 +40,42 @@ ScrollView } } } + Button + { + id: loadMoreButton + width: parent.width + height: UM.Theme.getSize("card").height + + 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 + + UM.RecolorImage + { + width: UM.Theme.getSize("small_button_icon").width + height: UM.Theme.getSize("small_button_icon").height + anchors.verticalCenter: loadMoreLabel.verticalCenter + + source: UM.Theme.getIcon("ArrowDown") + color: UM.Theme.getColor("primary") + } + Label + { + id: loadMoreLabel + text: catalog.i18nc("@button", "Load More") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("primary") + } + } + } } } \ No newline at end of file diff --git a/resources/themes/cura-light/icons/default/ArrowDown.svg b/resources/themes/cura-light/icons/default/ArrowDown.svg new file mode 100644 index 0000000000..ab5ea8e076 --- /dev/null +++ b/resources/themes/cura-light/icons/default/ArrowDown.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From 8776294932c3c5696940a4c92bdbf20a1574e05d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 18:23:19 +0200 Subject: Set loading state to False once parsing has completed This allows the user to request the next one. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 07ee103334..1d835dd802 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -99,6 +99,7 @@ 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) def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: """ -- cgit v1.2.3 From 46ad1ad077575f88a592cfbbd104735556d5b526 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 18:25:33 +0200 Subject: Add disabled state for load more button Can't click on the button then. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 010713eb4b..80184f1156 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -18,7 +18,10 @@ ScrollView Repeater { - model: Cura.PackageList{} + model: Cura.PackageList + { + id: pluginList + } delegate: Rectangle { @@ -46,6 +49,8 @@ ScrollView width: parent.width height: UM.Theme.getSize("card").height + enabled: pluginList.hasMore && !pluginList.isLoading + background: Rectangle { anchors.fill: parent @@ -61,19 +66,20 @@ ScrollView UM.RecolorImage { - width: UM.Theme.getSize("small_button_icon").width + width: visible ? UM.Theme.getSize("small_button_icon").width : 0 height: UM.Theme.getSize("small_button_icon").height anchors.verticalCenter: loadMoreLabel.verticalCenter + visible: pluginList.hasMore source: UM.Theme.getIcon("ArrowDown") - color: UM.Theme.getColor("primary") + color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") } Label { id: loadMoreLabel - text: catalog.i18nc("@button", "Load More") + text: pluginList.hasMore ? catalog.i18nc("@button", "Load More") : catalog.i18nc("@button", "No more results to load") font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("primary") + color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") } } } -- cgit v1.2.3 From e3cd5606f09f3a1794b47c6d160f7af60f2736e3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 18:26:29 +0200 Subject: Make load more button load more packages This adds the functionality of the button. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 1 + plugins/Marketplace/resources/qml/Plugins.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 1d835dd802..53d3c5909f 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -39,6 +39,7 @@ class PackageList(ListModel): self.request() + @pyqtSlot() def request(self) -> None: """ Make a request for the first paginated page of packages. diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 80184f1156..9cfcdac697 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -50,6 +50,7 @@ ScrollView height: UM.Theme.getSize("card").height enabled: pluginList.hasMore && !pluginList.isLoading + onClicked: pluginList.request() //Load next page in plug-in list. background: Rectangle { -- cgit v1.2.3 From 1ab677f5dd8abfd66519798c23b9ad3f79cff113 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 21 Oct 2021 18:37:02 +0200 Subject: Add state for when it's loading This has a slight bug in that the icon will immediately change to an arrow once loading has completed, but will slowly rotate back to angle 0. You don't see this, since the new plug-ins will come in between. The new plug-ins will always be a full page, or otherwise the icon disappears altogether and it's not visible anyway. But if you hold down the scrollbar while loading and quickly scroll down when loading completed, you can see this happen. I don't think anyone will really mind though. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 29 ++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 9cfcdac697..5f9c92c837 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -67,18 +67,41 @@ ScrollView UM.RecolorImage { + id: loadMoreIcon width: visible ? UM.Theme.getSize("small_button_icon").width : 0 height: UM.Theme.getSize("small_button_icon").height anchors.verticalCenter: loadMoreLabel.verticalCenter - visible: pluginList.hasMore - source: UM.Theme.getIcon("ArrowDown") + visible: pluginList.hasMore || pluginList.isLoading + source: UM.Theme.getIcon(pluginList.isLoading ? "ArrowDoubleCircleRight" : "ArrowDown") color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") + + RotationAnimator + { + target: loadMoreIcon + from: 0 + to: 360 + duration: 1000 + loops: Animation.Infinite + running: pluginList.isLoading + alwaysRunToEnd: true + } } Label { id: loadMoreLabel - text: pluginList.hasMore ? catalog.i18nc("@button", "Load More") : catalog.i18nc("@button", "No more results to load") + text: + { + if(pluginList.isLoading) + { + return catalog.i18nc("@button", "Loading"); + } + if(pluginList.hasMore) + { + return catalog.i18nc("@button", "Load more"); + } + return catalog.i18nc("@button", "No more results to load"); + } font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") } -- cgit v1.2.3 From 7796abd55b138df767a5aa971e7d54011487d1ba Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 00:51:26 +0200 Subject: Revert "Revert "Add new button to open the Marketplace"" This reverts commit aa4b7ddb8bce12c4ff2ea8864c1dad63a7a0a7c4. Apparently it is now in scope again for the button to be there. It's in the requirements and the requirements are holy. Whoopteedoo. Contributes to issue CURA-8556. --- resources/qml/MainWindow/MainWindowHeader.qml | 38 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 815ddff732..9112c733f3 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -86,7 +86,6 @@ Item // Shortcut button to quick access the Toolbox Controls2.Button { - id: marketplaceButton text: catalog.i18nc("@action:button", "Marketplace") height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) onClicked: Cura.Actions.browsePackages.trigger() @@ -107,7 +106,7 @@ Item anchors.fill: parent radius: parent.radius color: UM.Theme.getColor("primary_text") - opacity: marketplaceButton.hovered ? 0.2 : 0 + opacity: parent.hovered ? 0.2 : 0 Behavior on opacity { NumberAnimation { duration: 100 } } } } @@ -115,7 +114,7 @@ Item contentItem: Label { id: label - text: marketplaceButton.text + text: parent.text font: UM.Theme.getFont("default") color: UM.Theme.getColor("primary_text") width: contentWidth @@ -125,7 +124,7 @@ Item anchors { - right: applicationSwitcher.left + right: marketplaceButton.left rightMargin: UM.Theme.getSize("default_margin").width verticalCenter: parent.verticalCenter } @@ -150,6 +149,37 @@ Item } } + Controls2.Button + { + id: marketplaceButton + width: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) + height: width + anchors + { + verticalCenter: parent.verticalCenter + right: applicationSwitcher.left + rightMargin: UM.Theme.getSize("default_margin").width + } + + background: UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary_text") + source: UM.Theme.getIcon("Shop") + + Rectangle + { + anchors.fill: parent + radius: UM.Theme.getSize("action_button_radius").width + color: UM.Theme.getColor("primary_text") + opacity: marketplaceButton.hovered ? 0.2 : 0 + Behavior on opacity { NumberAnimation { duration: 100; } } + } + } + + onClicked: Cura.Actions.browsePackages.trigger() + } + ApplicationSwitcher { id: applicationSwitcher -- cgit v1.2.3 From aed52cea74621fb3789f655ddf3bc861d273ef56 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 01:11:26 +0200 Subject: Split functionality of the two marketplace buttons One now opens the old one again. One still opens the new one (but with a new name). Contributes to issue CURA-8556. --- resources/qml/Actions.qml | 8 ++++++++ resources/qml/MainWindow/ApplicationMenu.qml | 8 ++++++++ resources/qml/MainWindow/MainWindowHeader.qml | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index aa88c9176d..a440741dc0 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -72,6 +72,7 @@ Item property alias configureSettingVisibility: configureSettingVisibilityAction property alias browsePackages: browsePackagesAction + property alias openMarketplace: openMarketplaceAction UM.I18nCatalog{id: catalog; name: "cura"} @@ -483,4 +484,11 @@ Item text: catalog.i18nc("@action:menu", "&Marketplace") iconName: "plugins_browse" } + + Action + { + id: openMarketplaceAction + text: catalog.i18nc("@action:menu", "&Marketplace") + iconName: "plugins_browse" + } } diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index b7a016fabc..2924ae518f 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -201,6 +201,14 @@ Item { target: Cura.Actions.browsePackages function onTriggered() + { + curaExtensions.callExtensionMethod("Toolbox", "launch") + } + } + Connections + { + target: Cura.Actions.openMarketplace + function onTriggered() { curaExtensions.callExtensionMethod("Marketplace", "show") } diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 9112c733f3..48761c8c70 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -177,7 +177,7 @@ Item } } - onClicked: Cura.Actions.browsePackages.trigger() + onClicked: Cura.Actions.openMarketplace.trigger() } ApplicationSwitcher -- cgit v1.2.3 From bca2f36186ed08a0a56fc93331932955fbeb1fc9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 01:28:26 +0200 Subject: Use states rather than individually switching properties This is necessary because we'll add a fourth state here: An error state. This would get quite complex otherwise. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 57 +++++++++++++++++++-------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 5f9c92c837..46e0cf96e5 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -65,6 +65,43 @@ ScrollView spacing: UM.Theme.getSize("thin_margin").width + states: + [ + 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") + } + } + ] + UM.RecolorImage { id: loadMoreIcon @@ -72,9 +109,8 @@ ScrollView height: UM.Theme.getSize("small_button_icon").height anchors.verticalCenter: loadMoreLabel.verticalCenter - visible: pluginList.hasMore || pluginList.isLoading - source: UM.Theme.getIcon(pluginList.isLoading ? "ArrowDoubleCircleRight" : "ArrowDown") - color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") + source: UM.Theme.getIcon("ArrowDown") + color: UM.Theme.getColor("secondary_button_text") RotationAnimator { @@ -90,20 +126,9 @@ ScrollView Label { id: loadMoreLabel - text: - { - if(pluginList.isLoading) - { - return catalog.i18nc("@button", "Loading"); - } - if(pluginList.hasMore) - { - return catalog.i18nc("@button", "Load more"); - } - return catalog.i18nc("@button", "No more results to load"); - } + text: catalog.i18nc("@button", "Load more") font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor(loadMoreButton.enabled ? "secondary_button_text" : "action_button_disabled_text") + color: UM.Theme.getColor("secondary_button_text") } } } -- cgit v1.2.3 From daf450142b95958e544e0810d9d0d630686f1b38 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 01:56:57 +0200 Subject: Implement error handling and showing error state If an error occurs, the error message is stored in the list model, so that it can be shown with the list. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 35 +++++++++++++-- plugins/Marketplace/resources/qml/Plugins.qml | 63 ++++++++++++++++++++------- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 53d3c5909f..f4bb0374ba 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,10 +1,13 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt +from typing import Optional, TYPE_CHECKING + from cura.CuraApplication import CuraApplication from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import List, Optional, TYPE_CHECKING +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.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. @@ -16,6 +19,8 @@ if TYPE_CHECKING: from PyQt5.QtCore import QObject from PyQt5.QtNetwork import QNetworkReply +catalog = i18nCatalog("cura") + class PackageList(ListModel): """ Represents a list of packages to be displayed in the interface. @@ -34,6 +39,7 @@ 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") @@ -47,6 +53,7 @@ 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( @@ -83,6 +90,23 @@ class PackageList(ListModel): """ return self._request_url != "" + def setErrorMessage(self, error_message: str) -> None: + if(self._error_message != error_message): + self._error_message = error_message + self.errorMessageChanged.emit() + + errorMessageChanged = pyqtSignal() + + @pyqtProperty(str, notify = errorMessageChanged, fset = setErrorMessage) + def errorMessage(self) -> str: + """ + If an error occurred getting the list of packages, an error message will be held here. + + If no error occurred (yet), this will be an empty string. + :return: An error message, if any, or an empty string if everything went okay. + """ + return self._error_message + def _parseResponse(self, reply: "QNetworkReply") -> None: """ Parse the response from the package list API request. @@ -92,7 +116,9 @@ class PackageList(ListModel): """ response_data = HttpRequestManager.readJSON(reply) if "data" not in response_data or "links" not in response_data: - return # TODO: Handle invalid response. + Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") + self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) + return for package_data in response_data["data"]: package = PackageModel(package_data, parent = self) @@ -108,4 +134,5 @@ class PackageList(ListModel): :param reply: The reply with packages. This will most likely be incomplete and should be ignored. :param error: The error status of the request. """ - pass # TODO: Handle errors. + Logger.error(f"Could not reach Marketplace server.") + self.setErrorMessage(catalog.i18nc("@info:error", "Could not reach Marketplace.")) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 46e0cf96e5..4fee56f2fb 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -4,7 +4,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Cura 1.7 as Cura -import UM 1.0 as UM +import UM 1.4 as UM ScrollView { @@ -49,7 +49,7 @@ ScrollView width: parent.width height: UM.Theme.getSize("card").height - enabled: pluginList.hasMore && !pluginList.isLoading + enabled: pluginList.hasMore && !pluginList.isLoading || pluginList.errorMessage != "" onClicked: pluginList.request() //Load next page in plug-in list. background: Rectangle @@ -67,6 +67,26 @@ ScrollView 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" @@ -102,25 +122,38 @@ ScrollView } ] - UM.RecolorImage + Item { - id: loadMoreIcon - width: visible ? UM.Theme.getSize("small_button_icon").width : 0 + 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 - source: UM.Theme.getIcon("ArrowDown") - color: UM.Theme.getColor("secondary_button_text") + UM.StatusIcon + { + id: errorIcon + anchors.fill: parent - RotationAnimator + status: UM.StatusIcon.Status.ERROR + visible: false + } + UM.RecolorImage { - target: loadMoreIcon - from: 0 - to: 360 - duration: 1000 - loops: Animation.Infinite - running: pluginList.isLoading - alwaysRunToEnd: true + 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 -- cgit v1.2.3 From 37ccf5b8238054d540b7d226c6a3d033d33d0e4c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Oct 2021 10:37:02 +0200 Subject: Add missing return types on init CURA-8556 --- plugins/Marketplace/Marketplace.py | 3 ++- plugins/Marketplace/PackageList.py | 3 ++- plugins/Marketplace/PackageModel.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 506b8347e2..0dd5b29a5a 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -21,12 +21,13 @@ if TYPE_CHECKING: ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. + class Marketplace(Extension): """ The main managing object for the Marketplace plug-in. """ - def __init__(self): + def __init__(self) -> None: super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index f4bb0374ba..4980ec4a81 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: catalog = i18nCatalog("cura") + class PackageList(ListModel): """ Represents a list of packages to be displayed in the interface. @@ -33,7 +34,7 @@ class PackageList(ListModel): ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. - def __init__(self, parent: "QObject" = None): + def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) self._is_loading = True diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index eba9d63a6c..5ca25b370b 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -16,7 +16,7 @@ class PackageModel(QObject): QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_data: Dict[str, Any], parent: QObject = None): + def __init__(self, package_data: Dict[str, Any], parent: QObject = None) -> None: """ Constructs a new model for a single package. :param package_data: The data received from the Marketplace API about the package to create. -- cgit v1.2.3 From 43b84765725bf4f72abb2babcd0039c0f5d77cb3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Oct 2021 10:38:36 +0200 Subject: Remove unneeded parenthesis CURA-8556 --- plugins/Marketplace/PackageList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 4980ec4a81..99fb3a5b9e 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -68,7 +68,7 @@ class PackageList(ListModel): @pyqtSlot(bool) def setIsLoading(self, is_loading: bool) -> None: - if(is_loading != self._is_loading): + if is_loading != self._is_loading: self._is_loading = is_loading self.isLoadingChanged.emit() @@ -92,7 +92,7 @@ class PackageList(ListModel): return self._request_url != "" def setErrorMessage(self, error_message: str) -> None: - if(self._error_message != error_message): + if self._error_message != error_message: self._error_message = error_message self.errorMessageChanged.emit() -- cgit v1.2.3 From 797ff9c573a8dd3b31edadc101094354536cbaa2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 11:07:49 +0200 Subject: Turn new Marketplace button into button with text instead of icon The Marketplace icon is apparently confusing to use for the Marketplace in Cura and should only be used for the website version. Contributes to issue CURA-8556. --- resources/qml/MainWindow/MainWindowHeader.qml | 67 +++++++++++++++------------ 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 48761c8c70..a47f8e963c 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -84,9 +84,9 @@ Item } // Shortcut button to quick access the Toolbox - Controls2.Button + Controls2.Button //TODO: Remove once new Marketplace is completed. { - text: catalog.i18nc("@action:button", "Marketplace") + text: "Old Marketplace" height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) onClicked: Cura.Actions.browsePackages.trigger() @@ -128,32 +128,12 @@ Item rightMargin: UM.Theme.getSize("default_margin").width verticalCenter: parent.verticalCenter } - - Cura.NotificationIcon - { - id: marketplaceNotificationIcon - anchors - { - top: parent.top - right: parent.right - rightMargin: (-0.5 * width) | 0 - topMargin: (-0.5 * height) | 0 - } - visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 - - labelText: - { - const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length - return itemCount > 9 ? "9+" : itemCount - } - } } Controls2.Button { id: marketplaceButton - width: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) - height: width + height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) anchors { verticalCenter: parent.verticalCenter @@ -161,23 +141,52 @@ Item rightMargin: UM.Theme.getSize("default_margin").width } - background: UM.RecolorImage + hoverEnabled: true + onClicked: Cura.Actions.openMarketplace.trigger() + + contentItem: Label { - anchors.fill: parent + text: "Marketplace" //Ultimaker considers this a product name, so it shouldn't be translated. + font: UM.Theme.getFont("default") color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon("Shop") + width: contentWidth + verticalAlignment: Text.AlignVCenter + } + + background: Rectangle + { + radius: UM.Theme.getSize("action_button_radius").width + color: UM.Theme.getColor("main_window_header_background") + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("primary_text") Rectangle { anchors.fill: parent - radius: UM.Theme.getSize("action_button_radius").width + radius: parent.radius color: UM.Theme.getColor("primary_text") opacity: marketplaceButton.hovered ? 0.2 : 0 - Behavior on opacity { NumberAnimation { duration: 100; } } + Behavior on opacity { NumberAnimation { duration: 100 } } } } - onClicked: Cura.Actions.openMarketplace.trigger() + Cura.NotificationIcon + { + anchors + { + top: parent.top + right: parent.right + rightMargin: (-0.5 * width) | 0 + topMargin: (-0.5 * height) | 0 + } + visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 + + labelText: + { + const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length + return itemCount > 9 ? "9+" : itemCount + } + } } ApplicationSwitcher -- cgit v1.2.3 From 77d1bebbdb71db832e72279107a52d4acc6bb77e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 15:35:28 +0200 Subject: Put PackageList in Marketplace namespace This way it's not available to the rest of Cura, especially since PackageList is not such an uncommon name. It could give name collisions. Moreover, the rest of Cura doesn't need to have a list of packages from the Marketplace, so it's better separation. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 0dd5b29a5a..8c9f8097c8 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -31,7 +31,7 @@ class Marketplace(Extension): super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. - qmlRegisterType(PackageList, "Cura", 1, 7, "PackageList") + qmlRegisterType(PackageList, "Marketplace", 1, 0, "PackageList") @pyqtSlot() def show(self) -> None: diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 4fee56f2fb..6e78e9eff1 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -3,7 +3,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 -import Cura 1.7 as Cura +import Marketplace 1.0 as Marketplace import UM 1.4 as UM ScrollView @@ -18,7 +18,7 @@ ScrollView Repeater { - model: Cura.PackageList + model: Marketplace.PackageList { id: pluginList } -- cgit v1.2.3 From 476321be5c78251b81cc7045a930f12b9b75d493 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 15:38:54 +0200 Subject: Remove log entry for when Marketplace QML fails to load This is already logged with a warning by the QML engine. Contributes to issue CURA-8556. --- plugins/Marketplace/Marketplace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 8c9f8097c8..b3f573f792 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -47,7 +47,6 @@ class Marketplace(Extension): path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") self._window = CuraApplication.getInstance().createQmlComponent(path, {}) if self._window is None: # Still None? Failed to load the QML then. - Logger.error(f"Failed to load QML for Marketplace window.") return self._window.show() self._window.requestActivate() # Bring window into focus, if it was already open in the background. -- cgit v1.2.3 From 6b6b6f613fab5394b000c137415d34f78aabb5b9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 15:43:41 +0200 Subject: Remove superfluous pyqtSlot marking This can already be set via the isLoading property. What's more, it really only ever needs to be called from Python. I just added the fset because we have the setter anyway. Contributes to issue CURA-8556. --- plugins/Marketplace/PackageList.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 99fb3a5b9e..091ff4668e 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -66,7 +66,6 @@ class PackageList(ListModel): isLoadingChanged = pyqtSignal() - @pyqtSlot(bool) def setIsLoading(self, is_loading: bool) -> None: if is_loading != self._is_loading: self._is_loading = is_loading -- cgit v1.2.3 From f14a512718269cff97b56af21be47b5fdd61f158 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 16:17:23 +0200 Subject: Use ListView instead of Column The ListView works in mostly the same way, except it loads its contents asynchronously as they come into view. Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 244 +++++++++++++------------- 1 file changed, 124 insertions(+), 120 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 6e78e9eff1..a613ac9d4f 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,159 +10,163 @@ ScrollView { clip: true - Column + ListView { id: pluginColumn width: parent.width - spacing: UM.Theme.getSize("default_margin").height - Repeater + model: Marketplace.PackageList { - model: Marketplace.PackageList - { - id: pluginList - } + id: pluginList + } + spacing: UM.Theme.getSize("default_margin").height - delegate: Rectangle - { - width: pluginColumn.width - height: UM.Theme.getSize("card").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 + color: UM.Theme.getColor("main_background") + radius: UM.Theme.getSize("default_radius").width - Label - { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: (parent.height - height) / 2 + Label + { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: (parent.height - height) / 2 - text: model.package.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - } + text: model.package.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") } } - Button + + footer: Item //Wrapper item to add spacing between content and footer. { - id: loadMoreButton width: parent.width - height: UM.Theme.getSize("card").height + 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. + 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") - } + background: Rectangle + { + anchors.fill: parent + radius: UM.Theme.getSize("default_radius").width + color: UM.Theme.getColor("main_background") + } - Row - { - anchors.centerIn: parent + Row + { + anchors.centerIn: parent - spacing: UM.Theme.getSize("thin_margin").width + spacing: UM.Theme.getSize("thin_margin").width - states: - [ - State - { - name: "Error" - when: pluginList.errorMessage != "" - PropertyChanges + states: + [ + State { - 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 + 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 { - target: loadMoreIcon - source: UM.Theme.getIcon("ArrowDoubleCircleRight") - color: UM.Theme.getColor("action_button_disabled_text") - } - PropertyChanges + 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 { - target: loadMoreLabel - text: catalog.i18nc("@button", "Loading") - color: UM.Theme.getColor("action_button_disabled_text") + 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") + } } - }, - State + ] + + Item { - name: "LastPage" - when: !pluginList.hasMore - PropertyChanges + 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 { - target: loadMoreIcon + id: errorIcon + anchors.fill: parent + + status: UM.StatusIcon.Status.ERROR visible: false } - PropertyChanges + UM.RecolorImage { - target: loadMoreLabel - text: catalog.i18nc("@button", "No more results to load") - color: UM.Theme.getColor("action_button_disabled_text") + 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 + } } } - ] - - 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 + Label { - id: loadMoreIcon - anchors.fill: parent - - source: UM.Theme.getIcon("ArrowDown") + id: loadMoreLabel + text: catalog.i18nc("@button", "Load more") + font: UM.Theme.getFont("medium_bold") 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") - } } } } -- cgit v1.2.3 From 2d434a02e3649423339ef5ec887032c9c0f1d98e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 16:24:49 +0200 Subject: Align horizontal position of label to pixels Contributes to issue CURA-8556. --- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index a613ac9d4f..dca1b8762b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -33,7 +33,7 @@ ScrollView { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: (parent.height - height) / 2 + anchors.leftMargin: Math.round((parent.height - height) / 2) text: model.package.displayName font: UM.Theme.getFont("medium_bold") -- cgit v1.2.3 From a3c364d65adebcf8e6f3c961290940219f32a607 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 25 Oct 2021 16:28:25 +0200 Subject: Clarify import documentation Contributes to issue CURA-8556. --- 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 091ff4668e..257b71f6a0 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -13,7 +13,7 @@ from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To reques 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. -from .PackageModel import PackageModel # This list is a list of PackageModels. +from .PackageModel import PackageModel # The contents of this list. if TYPE_CHECKING: from PyQt5.QtCore import QObject -- cgit v1.2.3 From 7bbc91b7a5830aa9420b00e27290f832dc60849f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 27 Oct 2021 16:11:02 +0200 Subject: Clear contents of net marketplace if window is closed CURA-8556 --- plugins/Marketplace/resources/qml/Marketplace.qml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 2b17d3ebf3..04c26b6936 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -18,6 +18,18 @@ Window width: minimumWidth height: minimumHeight + onVisibleChanged: + { + // Set and unset the content. No need to keep things in memory if it's not visible. + if(visible) + { + content.source = "plugins.qml" + } + else + { + content.source = "" + } + } title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. modality: Qt.NonModal @@ -61,9 +73,9 @@ Window Loader //Page contents. { + id: content anchors.fill: parent anchors.margins: UM.Theme.getSize("default_margin").width - source: "Plugins.qml" } } -- cgit v1.2.3 From e3d90f16a195a236ef06f0c324cee06865f45886 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 27 Oct 2021 17:42:00 +0200 Subject: Close new marketplace window when signing out or in Because otherwise you can either see plugins you might not have the rights to, or not see plugins you do have the rights to. part of CURA-8556 --- plugins/Marketplace/resources/qml/Marketplace.qml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 04c26b6936..2213b3d456 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -7,6 +7,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Window 2.2 import UM 1.2 as UM +import Cura 1.6 as Cura Window { @@ -18,18 +19,18 @@ Window width: minimumWidth height: minimumHeight - onVisibleChanged: + // Set and unset the content. No need to keep things in memory if it's not visible. + onVisibleChanged: content.source = visible ? "Plugins.qml" : "" + + Connections { - // Set and unset the content. No need to keep things in memory if it's not visible. - if(visible) - { - content.source = "plugins.qml" - } - else + target: Cura.API.account + function onLoginStateChanged() { - content.source = "" + close(); } } + title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. modality: Qt.NonModal -- cgit v1.2.3 From 31dcf21a3ea8206ddbaf850584862e972ac08faf Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 28 Oct 2021 14:31:18 +0200 Subject: Disable horizontal scrollbar The layout of the plugin/material cards should take care of the text and rendering. The dimensions of these cards therefor should not require a horizontal scrollbar --- plugins/Marketplace/resources/qml/Plugins.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index dca1b8762b..0fbe8b7734 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -9,6 +9,7 @@ import UM 1.4 as UM ScrollView { clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ListView { @@ -170,4 +171,4 @@ ScrollView } } } -} \ No newline at end of file +} -- cgit v1.2.3 From cdf05a56065321d9fc15767490d846680f3dc63f Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 28 Oct 2021 16:07:21 +0200 Subject: Only show plugin and material packages It was showing all packages available in the marketplace. This included `cloud` DF integrations. It will now filter on packages and plugins. Contributes to CURA-8556 --- plugins/Marketplace/PackageList.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 257b71f6a0..b93cec1183 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -33,6 +33,7 @@ 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) @@ -121,8 +122,9 @@ class PackageList(ListModel): return for package_data in response_data["data"]: - package = PackageModel(package_data, parent = self) - self.appendItem({"package": package}) # Add it to this list model. + 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. self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self.hasMoreChanged.emit() -- cgit v1.2.3 From c31665f0691c0e0956cde4f1e3fcccd3a0da49f2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 17:34:55 +0200 Subject: 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. --- plugins/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 -- cgit v1.2.3 From 38b7f1761599da317b2336a744b2e9e2ca578218 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 17:49:32 +0200 Subject: Add basic tab bar to select pages Contributes to issue CURA-8557. --- plugins/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 -- cgit v1.2.3 From 5f884321eab555e12ab2d1cf3ec25053399b699e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:08:51 +0200 Subject: 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. --- plugins/Marketplace/resources/qml/Marketplace.qml | 6 +++-- .../Marketplace/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 -- cgit v1.2.3 From 4191f984409847e3d46fb087bd84cb45dd6a4ddc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:23:14 +0200 Subject: 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 -- cgit v1.2.3 From cbd1b8fbf738a3e9c09df0a8d8274f60d05eba9d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:59:03 +0200 Subject: 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 ++++++++++++++++++++++--- plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/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 -- cgit v1.2.3 From 4b86f7bb29ab48f071cf8cdd339c2beb24bc63f4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 21:45:58 +0200 Subject: 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() -- cgit v1.2.3 From 3e64b7cb6674708b9b4be843f885ce3a69fe8feb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 29 Oct 2021 10:10:57 +0200 Subject: 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 +++++++++++++++++--------- plugins/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 { -- cgit v1.2.3 From afe9c0c633015b04785e445602e5379443a7acc5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 29 Oct 2021 11:22:00 +0200 Subject: 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 -- cgit v1.2.3 From 7e674a18b3c243e277b19f200ba551f1033bb1cf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 29 Oct 2021 11:34:11 +0200 Subject: 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.") -- cgit v1.2.3 From 73bb311293af15649de3cf5eeb3f3cbb9ef20faa Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 29 Oct 2021 15:47:22 +0200 Subject: Add dark theme entry for background of detail panels Otherwise it uses the very bright light theme which doesn't match with the light text colour in dark mode. Contributes to issue CURA-8556. Found during issue CURA-8557. --- resources/themes/cura-dark/theme.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 520f863972..433c9fff92 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -6,6 +6,7 @@ "colors": { "main_background": [39, 44, 48, 255], + "detail_background": [63, 63, 63, 255], "message_background": [39, 44, 48, 255], "wide_lining": [31, 36, 39, 255], "thick_lining": [255, 255, 255, 60], -- cgit v1.2.3 From 03e1fc34b4ff59d5fffe281b090fae5196d2e5cf Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 29 Oct 2021 15:51:41 +0200 Subject: Change page title depending on selected tab Contributes to issue CURA-8557. --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 +++- plugins/Marketplace/resources/qml/PackageTypeTab.qml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 33cae9e778..6cc4a3622d 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -63,7 +63,7 @@ Window font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") - text: catalog.i18nc("@header", "Install Plugins") + text: pageSelectionTabBar.currentItem.pageTitle } } @@ -84,12 +84,14 @@ Window { width: implicitWidth text: catalog.i18nc("@button", "Plug-ins") + pageTitle: catalog.i18nc("@header", "Install Plugins") onClicked: content.source = "Plugins.qml" } PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Materials") + pageTitle: catalog.i18nc("@header", "Install Materials") onClicked: content.source = "Materials.qml" } } diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml index 9b6136f1f0..31fbabc294 100644 --- a/plugins/Marketplace/resources/qml/PackageTypeTab.qml +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -7,6 +7,8 @@ import UM 1.0 as UM TabButton { + property string pageTitle + background: Rectangle { anchors.fill: parent -- cgit v1.2.3 From 86d5d315bc3d5974be279061c1667f4bbf02caaa Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 1 Nov 2021 17:02:07 +0100 Subject: Differentiate between local and remote packages There is a distinction between packages which are already installed on the local machine and packages which are available on the remote server. Even with this difference it is important that they are handled the same and can be reused in the same GUI elements. In order to reduce code duplication I created a parent object PackageList which contains the base logic and interface for the QML and let both RemotePackageList and LocalPackageList inherit from this. UX specified that the gear icon (Settings.svg) should be separate from the tabs of material and plugins. This also ment that the current tab item couldn't set the pageTitle anymore. This is now defined in the Package component and set when the loader has loaded the external QML file. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 48 +++++++ plugins/Marketplace/Marketplace.py | 6 +- plugins/Marketplace/PackageList.py | 138 ++++----------------- plugins/Marketplace/RemotePackageList.py | 131 +++++++++++++++++++ .../Marketplace/resources/qml/ManagedPackages.qml | 16 +++ plugins/Marketplace/resources/qml/Marketplace.qml | 78 ++++++++++-- plugins/Marketplace/resources/qml/Materials.qml | 5 +- plugins/Marketplace/resources/qml/Packages.qml | 10 +- plugins/Marketplace/resources/qml/Plugins.qml | 5 +- .../themes/cura-light/icons/default/Settings.svg | 3 + .../themes/cura-light/icons/high/Settings.svg | 3 + 11 files changed, 307 insertions(+), 136 deletions(-) create mode 100644 plugins/Marketplace/LocalPackageList.py create mode 100644 plugins/Marketplace/RemotePackageList.py create mode 100644 plugins/Marketplace/resources/qml/ManagedPackages.qml create mode 100644 resources/themes/cura-light/icons/default/Settings.svg create mode 100644 resources/themes/cura-light/icons/high/Settings.svg diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py new file mode 100644 index 0000000000..ff221ed606 --- /dev/null +++ b/plugins/Marketplace/LocalPackageList.py @@ -0,0 +1,48 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import pyqtSlot, Qt +from typing import TYPE_CHECKING + +from UM.i18n import i18nCatalog + +from cura.CuraApplication import CuraApplication + +from .PackageList import PackageList +from .PackageModel import PackageModel # The contents of this list. + +if TYPE_CHECKING: + from PyQt5.QtCore import QObject + +catalog = i18nCatalog("cura") + + +class LocalPackageList(PackageList): + PackageRole = Qt.UserRole + 1 + + def __init__(self, parent: "QObject" = None) -> None: + super().__init__(parent) + self._application = CuraApplication.getInstance() + + @pyqtSlot() + def updatePackages(self) -> None: + """ + Make a request for the first paginated page of packages. + + When the request is done, the list will get updated with the new package models. + """ + self.setErrorMessage("") # Clear any previous errors. + self.setIsLoading(True) + self._getLocalPackages() + + def _getLocalPackages(self) -> None: + plugin_registry = self._application.getPluginRegistry() + package_manager = self._application.getPackageManager() + + bundled = plugin_registry.getInstalledPlugins() + for b in bundled: + package = PackageModel({"package_id": b, "display_name": b}, parent = self) + self.appendItem({"package": package}) + packages = package_manager.getInstalledPackageIDs() + self.setIsLoading(False) + self.setHasMore(False) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index b3f573f792..9418174d19 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -13,7 +13,8 @@ from UM.Extension import Extension # We are implementing the main object of an from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). -from .PackageList import PackageList # To register this type with QML. +from .RemotePackageList import RemotePackageList # To register this type with QML. +from .LocalPackageList import LocalPackageList # To register this type with QML. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -31,7 +32,8 @@ class Marketplace(Extension): super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. - qmlRegisterType(PackageList, "Marketplace", 1, 0, "PackageList") + qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") + qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") @pyqtSlot() def show(self) -> None: diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 424b66fe21..00532313f0 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -2,19 +2,10 @@ # 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 typing import TYPE_CHECKING -from cura.CuraApplication import CuraApplication -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from UM.i18n import i18nCatalog -from UM.Logger import Logger from UM.Qt.ListModel import ListModel -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. -from .PackageModel import PackageModel # The contents of this list. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -23,99 +14,60 @@ catalog = i18nCatalog("cura") class PackageList(ListModel): - """ - Represents a list of packages to be displayed in the interface. - - The list can be filtered (e.g. on package type, materials vs. plug-ins) and - paginated. - """ - PackageRole = Qt.UserRole + 1 - ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. - def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) - - self._ongoing_request: Optional[HttpRequestData] = None - self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._error_message = "" - - self._package_type_filter = "" - self._request_url = self._initialRequestUrl() - 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() + self._is_loading = False + self._has_more = False @pyqtSlot() - def request(self) -> None: + def updatePackages(self) -> None: """ - Make a request for the first paginated page of packages. - - When the request is done, the list will get updated with the new package models. + Initialize the first page of packages """ self.setErrorMessage("") # Clear any previous errors. - - http = HttpRequestManager.getInstance() - 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 abortUpdating(self) -> None: + pass def reset(self) -> None: self.clear() - self._request_url = self._initialRequestUrl() isLoadingChanged = pyqtSignal() - @pyqtProperty(bool, notify = isLoadingChanged) + def setIsLoading(self, value: bool) -> None: + if self._is_loading != value: + self._is_loading = value + self.isLoadingChanged.emit() + + @pyqtProperty(bool, fset = setIsLoading, 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: ``True`` if the list is being gathered, or ``False`` if . """ - return self._ongoing_request is not None + return self._is_loading hasMoreChanged = pyqtSignal() - @pyqtProperty(bool, notify = hasMoreChanged) + def setHasMore(self, value: bool) -> None: + if self._has_more != value: + self._has_more = value + self.hasMoreChanged.emit() + + @pyqtProperty(bool, fset = setHasMore, notify = hasMoreChanged) def hasMore(self) -> bool: """ Returns whether there are more packages to load. :return: ``True`` if there are more packages to load, or ``False`` if we've reached the last page of the pagination. """ - 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 + return self._has_more def setErrorMessage(self, error_message: str) -> None: if self._error_message != error_message: @@ -133,49 +85,3 @@ class PackageList(ListModel): :return: An error message, if any, or an empty string if everything went okay. """ 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. - - This converts that response into PackageModels, and triggers the ListModel to update. - :param reply: A reply containing information about a number of packages. - """ - response_data = HttpRequestManager.readJSON(reply) - if "data" not in response_data or "links" not in response_data: - Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") - self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) - return - - for package_data in response_data["data"]: - 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() - self._ongoing_request = None - self.isLoadingChanged.emit() - - 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. - """ - if error == QNetworkReply.NetworkError.OperationCanceledError: - 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.") - self.setErrorMessage(catalog.i18nc("@info:error", "Could not reach Marketplace.")) - self._ongoing_request = None - self.isLoadingChanged.emit() diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py new file mode 100644 index 0000000000..3cfb11d6ba --- /dev/null +++ b/plugins/Marketplace/RemotePackageList.py @@ -0,0 +1,131 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot +from PyQt5.QtNetwork import QNetworkReply +from typing import Optional, TYPE_CHECKING + +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. +from UM.i18n import i18nCatalog +from UM.Logger import Logger +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. +from .PackageList import PackageList +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") + + +class RemotePackageList(PackageList): + ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. + + def __init__(self, parent: "QObject" = None) -> None: + super().__init__(parent) + + self._ongoing_request: Optional[HttpRequestData] = None + self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + + self._package_type_filter = "" + self._request_url = self._initialRequestUrl() + + 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.abortUpdating() + + @pyqtSlot() + def updatePackages(self) -> None: + """ + Make a request for the first paginated page of packages. + + When the request is done, the list will get updated with the new package models. + """ + self.setErrorMessage("") # Clear any previous errors. + self.setIsLoading(True) + + self._ongoing_request = HttpRequestManager.getInstance().get( + self._request_url, + scope = self._scope, + callback = self._parseResponse, + error_callback = self._onError + ) + + @pyqtSlot() + def abortUpdating(self) -> None: + HttpRequestManager.getInstance().abortRequest(self._ongoing_request) + self._ongoing_request = None + + def reset(self) -> None: + self.clear() + self._request_url = self._initialRequestUrl() + + 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, fset = setPackageTypeFilter, notify = packageTypeFilterChanged) + 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 _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. + + This converts that response into PackageModels, and triggers the ListModel to update. + :param reply: A reply containing information about a number of packages. + """ + response_data = HttpRequestManager.readJSON(reply) + if "data" not in response_data or "links" not in response_data: + Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") + self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) + return + + for package_data in response_data["data"]: + 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._ongoing_request = None + self.setIsLoading(False) + self.setHasMore(self._request_url != "") + + 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. + """ + if error == QNetworkReply.NetworkError.OperationCanceledError: + 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.") + self.setErrorMessage(catalog.i18nc("@info:error", "Could not reach Marketplace.")) + self._ongoing_request = None + self.setIsLoading(False) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml new file mode 100644 index 0000000000..243d5bf12e --- /dev/null +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -0,0 +1,16 @@ +// 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.15 + +import Marketplace 1.0 as Marketplace +import UM 1.4 as UM + +Packages +{ + pageTitle: catalog.i18nc("@header", "Manage packages") + model: Marketplace.LocalPackageList + { + } +} diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 6cc4a3622d..7004b69d46 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -34,7 +34,8 @@ Window title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. modality: Qt.NonModal - Rectangle //Background color. + // Background color + Rectangle { anchors.fill: parent color: UM.Theme.getColor("main_background") @@ -45,13 +46,15 @@ Window spacing: UM.Theme.getSize("default_margin").height - Item //Page title. + // Page title. + Item { Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height Label { + id: pageTitle anchors { left: parent.left @@ -63,7 +66,7 @@ Window font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") - text: pageSelectionTabBar.currentItem.pageTitle + text: "" } } @@ -72,45 +75,100 @@ Window Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height - TabBar //Page selection. + Button { - id: pageSelectionTabBar + id: managePackagesButton + + hoverEnabled: true + + width: childrenRect.width + height: childrenRect.height + anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width + background: Rectangle + { + color: UM.Theme.getColor("action_button") + border.color: "transparent" + border.width: UM.Theme.getSize("default_lining").width + } + + Cura.ToolTip + { + id: managePackagesTooltip + + tooltipText: catalog.i18nc("@info:tooltip", "Manage packages") + arrowSize: 0 + visible: managePackagesButton.hovered + } + + UM.RecolorImage + { + id: managePackagesIcon + + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + + color: UM.Theme.getColor("icon") + source: UM.Theme.getIcon("Settings") + } + + onClicked: + { + content.source = "ManagedPackages.qml" + } + } + + // Page selection. + TabBar + { + id: pageSelectionTabBar + anchors.right: managePackagesButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + spacing: 0 PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Plug-ins") - pageTitle: catalog.i18nc("@header", "Install Plugins") onClicked: content.source = "Plugins.qml" } PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Materials") - pageTitle: catalog.i18nc("@header", "Install Materials") onClicked: content.source = "Materials.qml" } } } - Rectangle //Page contents. + // Page contents. + Rectangle { Layout.preferredWidth: parent.width Layout.fillHeight: true color: UM.Theme.getColor("detail_background") - Loader //Page contents. + // Page contents. + Loader { id: content anchors.fill: parent anchors.margins: UM.Theme.getSize("default_margin").width source: "Plugins.qml" + + Connections + { + target: content + onLoaded: function() + { + pageTitle.text = content.item.pageTitle + } + } } } } } -} \ No newline at end of file +} diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 4f3c59d9fb..1d1572976a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -5,8 +5,9 @@ import Marketplace 1.0 as Marketplace Packages { - model: Marketplace.PackageList + pageTitle: catalog.i18nc("@header", "Install Materials") + model: Marketplace.RemotePackageList { 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 5442247668..cdc066626c 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -12,9 +12,10 @@ ScrollView ScrollBar.horizontal.policy: ScrollBar.AlwaysOff property alias model: packagesListview.model + property string pageTitle - Component.onCompleted: model.request() - Component.onDestruction: model.abortRequest() + Component.onCompleted: model.updatePackages() + Component.onDestruction: model.abortUpdating() ListView { @@ -43,7 +44,8 @@ ScrollView } } - footer: Item //Wrapper item to add spacing between content and footer. + //Wrapper item to add spacing between content and footer. + footer: Item { width: parent.width height: UM.Theme.getSize("card").height + packagesListview.spacing @@ -55,7 +57,7 @@ ScrollView 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. + onClicked: packages.model.updatePackages() //Load next page in plug-in list. background: Rectangle { diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 71814f54ad..ef5d92c2e8 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -5,8 +5,9 @@ import Marketplace 1.0 as Marketplace Packages { - model: Marketplace.PackageList + pageTitle: catalog.i18nc("@header", "Install Plugins") + model: Marketplace.RemotePackageList { packageTypeFilter: "plugin" } -} \ No newline at end of file +} diff --git a/resources/themes/cura-light/icons/default/Settings.svg b/resources/themes/cura-light/icons/default/Settings.svg new file mode 100644 index 0000000000..feb0ab0cc8 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/themes/cura-light/icons/high/Settings.svg b/resources/themes/cura-light/icons/high/Settings.svg new file mode 100644 index 0000000000..1cd2ff324e --- /dev/null +++ b/resources/themes/cura-light/icons/high/Settings.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From b53a9840f3138893dec2e6c562aa6e4167cb0f97 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Mon, 1 Nov 2021 17:02:07 +0100 Subject: Moved ManagePackagesButton to its own file For better readability Contributes to CURA-8558 --- .../resources/qml/ManagePackagesButton.qml | 44 ++++++++++++++++++++++ plugins/Marketplace/resources/qml/Marketplace.qml | 34 +---------------- 2 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/ManagePackagesButton.qml diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml new file mode 100644 index 0000000000..e6c1406858 --- /dev/null +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import UM 1.2 as UM +import Cura 1.6 as Cura + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Button +{ + id: root + width: childrenRect.width + height: childrenRect.height + + hoverEnabled: true + + background: Rectangle + { + color: UM.Theme.getColor("action_button") + border.color: "transparent" + border.width: UM.Theme.getSize("default_lining").width + } + + Cura.ToolTip + { + id: tooltip + + tooltipText: catalog.i18nc("@info:tooltip", "Manage packages") + arrowSize: 0 + visible: root.hovered + } + + UM.RecolorImage + { + id: icon + + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + + color: UM.Theme.getColor("icon") + source: UM.Theme.getIcon("Settings") + } +} diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 7004b69d46..bbe5b2b9e9 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -75,45 +75,13 @@ Window Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height - Button + ManagePackagesButton { id: managePackagesButton - hoverEnabled: true - - width: childrenRect.width - height: childrenRect.height - anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width - background: Rectangle - { - color: UM.Theme.getColor("action_button") - border.color: "transparent" - border.width: UM.Theme.getSize("default_lining").width - } - - Cura.ToolTip - { - id: managePackagesTooltip - - tooltipText: catalog.i18nc("@info:tooltip", "Manage packages") - arrowSize: 0 - visible: managePackagesButton.hovered - } - - UM.RecolorImage - { - id: managePackagesIcon - - width: UM.Theme.getSize("section_icon").width - height: UM.Theme.getSize("section_icon").height - - color: UM.Theme.getColor("icon") - source: UM.Theme.getIcon("Settings") - } - onClicked: { content.source = "ManagedPackages.qml" -- cgit v1.2.3 From 397baebda45e44fc7d1e182394c514a56b3703ea Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Tue, 2 Nov 2021 08:54:57 +0100 Subject: Changed deprecated qml syntax Contributes to CURA-8558 --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index bbe5b2b9e9..3adedd9cf0 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -130,7 +130,7 @@ Window Connections { target: content - onLoaded: function() + function onLoaded() { pageTitle.text = content.item.pageTitle } -- cgit v1.2.3 From c4c99f665726cb0f25444cfacaa0c62ac0b605f8 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Tue, 2 Nov 2021 14:31:12 +0100 Subject: Added sections to the packagelists By providing a `section_title` with a string to the `package_data` packages can be subdivided in sections, each with its own header. For remote packages this will be `None` and therefore no sections are created there. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 5 ++++- plugins/Marketplace/PackageModel.py | 7 ++++++- plugins/Marketplace/resources/qml/Packages.qml | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index ff221ed606..f71c5e4f06 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -41,8 +41,11 @@ class LocalPackageList(PackageList): bundled = plugin_registry.getInstalledPlugins() for b in bundled: - package = PackageModel({"package_id": b, "display_name": b}, parent = self) + package = PackageModel({"package_id": b, "display_name": b, "section_title": "bundled"}, parent = self) self.appendItem({"package": package}) packages = package_manager.getInstalledPackageIDs() + for p in packages: + package = PackageModel({"package_id": p, "display_name": p, "section_title": "package"}, parent = self) + self.appendItem({"package": package}) self.setIsLoading(False) self.setHasMore(False) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 5ca25b370b..0ade18839e 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, QObject -from typing import Any, Dict +from typing import Any, Dict, Optional from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. @@ -25,6 +25,7 @@ class PackageModel(QObject): super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) + self._section_title = package_data.get("section_title", None) @pyqtProperty(str, constant = True) def packageId(self) -> str: @@ -33,3 +34,7 @@ class PackageModel(QObject): @pyqtProperty(str, constant = True) def displayName(self) -> str: return self._display_name + + @pyqtProperty(str, constant = True) + def sectionTitle(self) -> Optional[str]: + return self._section_title diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index cdc066626c..e5414e3f67 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -24,6 +24,29 @@ ScrollView spacing: UM.Theme.getSize("default_margin").height + section.property: "package.sectionTitle" + section.criteria: ViewSection.FullString + section.delegate: Rectangle + { + width: packagesListview.width + height: sectionHeaderText.implicitHeight + UM.Theme.getSize("default_margin").height + + color: UM.Theme.getColor("detail_background") + + required property string section + + Label + { + id: sectionHeaderText + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + + text: parent.section + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + } + } + delegate: Rectangle { width: packagesListview.width -- cgit v1.2.3 From 3f700e5d0c81bd68dfe94030c880f3b1dbfaea54 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Tue, 2 Nov 2021 14:33:53 +0100 Subject: Only show Footer when the packagelist is paginated It doesn't make sense to show a footer when items are retrieved in one go. Except when an error occurs. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 2 ++ plugins/Marketplace/PackageList.py | 5 +++++ plugins/Marketplace/resources/qml/Packages.qml | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index f71c5e4f06..44eaac9a0d 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import pyqtSlot, Qt from typing import TYPE_CHECKING from UM.i18n import i18nCatalog +from UM.Logger import Logger from cura.CuraApplication import CuraApplication @@ -23,6 +24,7 @@ class LocalPackageList(PackageList): def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) self._application = CuraApplication.getInstance() + self._has_footer = False @pyqtSlot() def updatePackages(self) -> None: diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 00532313f0..de07e5e2fb 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -22,6 +22,7 @@ class PackageList(ListModel): self.addRoleName(self.PackageRole, "package") self._is_loading = False self._has_more = False + self._has_footer = True @pyqtSlot() def updatePackages(self) -> None: @@ -85,3 +86,7 @@ class PackageList(ListModel): :return: An error message, if any, or an empty string if everything went okay. """ return self._error_message + + @pyqtProperty(bool, constant = True) + def hasFooter(self) -> bool: + return self._has_footer diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index e5414e3f67..8ab241e784 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -71,7 +71,8 @@ ScrollView footer: Item { width: parent.width - height: UM.Theme.getSize("card").height + packagesListview.spacing + height: model.hasFooter || packages.model.errorMessage != "" ? UM.Theme.getSize("card").height + packagesListview.spacing : 0 + visible: model.hasFooter || packages.model.errorMessage != "" Button { id: loadMoreButton -- cgit v1.2.3 From 1b7b0b9caf5b5ce26e8b94e72b234bfdefdb7dbe Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Tue, 2 Nov 2021 16:20:25 +0100 Subject: Sort the different sections and packages The order in which UX defined the different sections are: - Installed Cura Plugins - Installed Materials - Bundled Plugins - Bundled Materials All packages need to be order at least by section, but I also took the liberty to sort the packages in these sections by Alphabet. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 44eaac9a0d..cf81cd5dde 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -20,6 +20,18 @@ catalog = i18nCatalog("cura") class LocalPackageList(PackageList): PackageRole = Qt.UserRole + 1 + PACKAGE_SECTION_HEADER = { + "installed": + { + "plugin": catalog.i18nc("@label:property", "Installed Cura Plugins"), + "material": catalog.i18nc("@label:property", "Installed Materials") + }, + "bundled": + { + "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), + "material": catalog.i18nc("@label:property", "Bundled Materials") + } + } def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) @@ -38,16 +50,29 @@ class LocalPackageList(PackageList): self._getLocalPackages() def _getLocalPackages(self) -> None: - plugin_registry = self._application.getPluginRegistry() - package_manager = self._application.getPackageManager() - - bundled = plugin_registry.getInstalledPlugins() - for b in bundled: - package = PackageModel({"package_id": b, "display_name": b, "section_title": "bundled"}, parent = self) - self.appendItem({"package": package}) - packages = package_manager.getInstalledPackageIDs() - for p in packages: - package = PackageModel({"package_id": p, "display_name": p, "section_title": "package"}, parent = self) - self.appendItem({"package": package}) + sorted_sections = {} + for section in self._getSections(): + packages = filter(lambda p: p["section_title"] == section, self._allPackageInfo()) + sorted_sections[section] = sorted(packages, key = lambda p: p["display_name"]) + + for section in sorted_sections.values(): + for package_data in section: + package = PackageModel(package_data, parent = self) + self.appendItem({"package": package}) + self.setIsLoading(False) self.setHasMore(False) + + def _getSections(self): + for package_type in self.PACKAGE_SECTION_HEADER.values(): + for section in package_type.values(): + yield section + + def _allPackageInfo(self): + manager = self._application.getPackageManager() + for package_id in manager.getAllInstalledPackageIDs(): + package_data = manager.getInstalledPackageInfo(package_id) + bundled_or_installed = "bundled" if package_data["is_bundled"] else "installed" + package_type = package_data["package_type"] + package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] + yield package_data -- cgit v1.2.3 From 5db6e50dee2cebd2d31f4b4e9aa1eb7f73c9f4c2 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Tue, 2 Nov 2021 16:21:02 +0100 Subject: Fixed typo not plug-ins but plugins Contributes to CURA-8558 --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 3adedd9cf0..2d95904257 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -100,7 +100,7 @@ Window PackageTypeTab { width: implicitWidth - text: catalog.i18nc("@button", "Plug-ins") + text: catalog.i18nc("@button", "Plugins") onClicked: content.source = "Plugins.qml" } PackageTypeTab -- cgit v1.2.3 From 7c5bfef318d0e0a2cb1321ba531d0d5a8dad0c78 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 2 Nov 2021 16:47:08 +0100 Subject: Fix crash when switching between material & plugin tab --- plugins/Marketplace/PackageList.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 424b66fe21..4ebbe8d349 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -157,8 +157,13 @@ class PackageList(ListModel): return for package_data in response_data["data"]: - package = PackageModel(package_data, parent = self) - self.appendItem({"package": package}) # Add it to this list model. + try: + package = PackageModel(package_data, parent = self) + self.appendItem({"package": package}) # Add it to this list model. + except RuntimeError: + # I've tried setting the ownership of this object to not qml, but unfortunately that didn't prevent + # the issue that the wrapped C++ object was deleted when it was still parsing the response + return self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self.hasMoreChanged.emit() -- cgit v1.2.3 From 8fad2e0f39edb59d2e2f7fba11def1787af08c8a Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 10:33:00 +0100 Subject: Changed section header for Installed Plugins As agreed with UX changed: `Installed Cura Plugins` to `Installed Plugins` Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index cf81cd5dde..171751c238 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -23,7 +23,7 @@ class LocalPackageList(PackageList): PACKAGE_SECTION_HEADER = { "installed": { - "plugin": catalog.i18nc("@label:property", "Installed Cura Plugins"), + "plugin": catalog.i18nc("@label:property", "Installed Plugins"), "material": catalog.i18nc("@label:property", "Installed Materials") }, "bundled": -- cgit v1.2.3 From 080e3b9f27610aa2ea6147be9b0f423d9bb4a144 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 12:04:10 +0100 Subject: To be removed packages are still listed for the current session A user might still need to interact with a **to be removed** package and it is also still being used in the current Cura session. But the current package list doesn't list that package anymore. Introduced a `getPackagesToRemove()` method in the Uranium PackageManager to circumvent this issue. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 171751c238..589ba26226 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -70,9 +70,11 @@ class LocalPackageList(PackageList): def _allPackageInfo(self): manager = self._application.getPackageManager() - for package_id in manager.getAllInstalledPackageIDs(): - package_data = manager.getInstalledPackageInfo(package_id) - bundled_or_installed = "bundled" if package_data["is_bundled"] else "installed" - package_type = package_data["package_type"] - package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] - yield package_data + for package_type, packages in manager.getAllInstalledPackagesInfo().items(): + for package_data in packages: + bundled_or_installed = "installed" if manager.isUserInstalledPackage(package_data["package_id"]) else "bundled" + package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] + yield package_data + + for package_data in manager.getPackagesToRemove().values(): + yield package_data["package_info"] -- cgit v1.2.3 From 02187035925110c26970eb0241de2f7acb746dd1 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 12:15:56 +0100 Subject: To be installed packages are still listed for the current session A user might still need to interact with a **to be installed** package. But the current package list doesn't list that package anymore. Introduced a `getPackagesToInstall()` method in the Uranium PackageManager to circumvent this issue. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 589ba26226..e672c191f6 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -78,3 +78,9 @@ class LocalPackageList(PackageList): for package_data in manager.getPackagesToRemove().values(): yield package_data["package_info"] + + for package_data in manager.getPackagesToInstall().values(): + package_info = package_data["package_info"] + package_type = package_info["package_type"] + package_info["section_title"] = self.PACKAGE_SECTION_HEADER["installed"][package_type] + yield package_info -- cgit v1.2.3 From edc71f12a323b81c6caf34623d84d175a815a275 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 13:43:08 +0100 Subject: Updated documentation and typing Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 35 +++++++++++++++++++++--------- plugins/Marketplace/Marketplace.py | 2 +- plugins/Marketplace/PackageList.py | 38 +++++++++++++++++---------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index e672c191f6..aad1bbbaa1 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -2,10 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtSlot, Qt -from typing import TYPE_CHECKING +from typing import Any, Dict, Generator, TYPE_CHECKING from UM.i18n import i18nCatalog -from UM.Logger import Logger from cura.CuraApplication import CuraApplication @@ -31,7 +30,7 @@ class LocalPackageList(PackageList): "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), "material": catalog.i18nc("@label:property", "Bundled Materials") } - } + } # The section headers to be used for the different package categories def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) @@ -40,42 +39,58 @@ class LocalPackageList(PackageList): @pyqtSlot() def updatePackages(self) -> None: - """ - Make a request for the first paginated page of packages. - - When the request is done, the list will get updated with the new package models. + """Update the list with local packages, these are materials or plugin, either bundled or user installed. The list + will also contain **to be removed** or **to be installed** packages since the user might still want to interact + with these. """ self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) self._getLocalPackages() + self.setIsLoading(True) def _getLocalPackages(self) -> None: + """ Obtain the local packages. + The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages + for the sections are sorted alphabetically on the display name + """ + sorted_sections = {} + # Filter the package list per section_title and sort these for section in self._getSections(): packages = filter(lambda p: p["section_title"] == section, self._allPackageInfo()) sorted_sections[section] = sorted(packages, key = lambda p: p["display_name"]) + # Create a PackageModel from the sorted package_info and append them to the list for section in sorted_sections.values(): for package_data in section: package = PackageModel(package_data, parent = self) self.appendItem({"package": package}) self.setIsLoading(False) - self.setHasMore(False) + self.setHasMore(False) # All packages should have been loaded at this time - def _getSections(self): + def _getSections(self) -> Generator[str]: + """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the + correct order""" for package_type in self.PACKAGE_SECTION_HEADER.values(): for section in package_type.values(): yield section - def _allPackageInfo(self): + def _allPackageInfo(self) -> Generator[Dict[str, Any]]: + """ A generator which returns a unordered list of package_info, the section_title is appended to the each + package_info""" + manager = self._application.getPackageManager() + + # Get all the installed packages, add a section_title depending on package_type and user installed for package_type, packages in manager.getAllInstalledPackagesInfo().items(): for package_data in packages: bundled_or_installed = "installed" if manager.isUserInstalledPackage(package_data["package_id"]) else "bundled" package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] yield package_data + # Get all to be removed package_info's. These packages are still used in the current session so the user might + # to interact with these in the list for package_data in manager.getPackagesToRemove().values(): yield package_data["package_info"] diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 9418174d19..18d80d6e68 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -9,8 +9,8 @@ from typing import Optional, TYPE_CHECKING from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. from cura.UltimakerCloud import UltimakerCloudConstants + from UM.Extension import Extension # We are implementing the main object of an extension here. -from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). from .RemotePackageList import RemotePackageList # To register this type with QML. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index de07e5e2fb..17a755255b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -14,6 +14,9 @@ catalog = i18nCatalog("cura") class PackageList(ListModel): + """ A List model for Packages, this class serves as parent class for more detailed implementations. + such as Packages obtained from Remote or Local source + """ PackageRole = Qt.UserRole + 1 def __init__(self, parent: "QObject" = None) -> None: @@ -26,20 +29,20 @@ class PackageList(ListModel): @pyqtSlot() def updatePackages(self) -> None: - """ - Initialize the first page of packages - """ - self.setErrorMessage("") # Clear any previous errors. - self.isLoadingChanged.emit() + """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" + pass @pyqtSlot() def abortUpdating(self) -> None: + """ A Qt slot which allows the update process to be aborted. Override this for child classes with async/callback + updatePackges methods""" pass def reset(self) -> None: + """ Resets and clears the list""" self.clear() - isLoadingChanged = pyqtSignal() + isLoadingChanged = pyqtSignal() # The signal for isLoading property def setIsLoading(self, value: bool) -> None: if self._is_loading != value: @@ -48,13 +51,12 @@ class PackageList(ListModel): @pyqtProperty(bool, fset = setIsLoading, 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 being gathered, or ``False`` if . + """ Indicating if the the packages are loading + :return" ``True`` if the list is being obtained, otherwise ``False`` """ return self._is_loading - hasMoreChanged = pyqtSignal() + hasMoreChanged = pyqtSignal() # The signal for hasMore property def setHasMore(self, value: bool) -> None: if self._has_more != value: @@ -63,24 +65,21 @@ class PackageList(ListModel): @pyqtProperty(bool, fset = setHasMore, notify = hasMoreChanged) def hasMore(self) -> bool: - """ - Returns whether there are more packages to load. - :return: ``True`` if there are more packages to load, or ``False`` if we've reached the last page of the - pagination. + """ Indicating if there are more packages available to load. + :return: ``True`` if there are more packages to load, or ``False``. """ return self._has_more + errorMessageChanged = pyqtSignal() # The signal for errorMessage property + def setErrorMessage(self, error_message: str) -> None: if self._error_message != error_message: self._error_message = error_message self.errorMessageChanged.emit() - errorMessageChanged = pyqtSignal() - @pyqtProperty(str, notify = errorMessageChanged, fset = setErrorMessage) def errorMessage(self) -> str: - """ - If an error occurred getting the list of packages, an error message will be held here. + """ If an error occurred getting the list of packages, an error message will be held here. If no error occurred (yet), this will be an empty string. :return: An error message, if any, or an empty string if everything went okay. @@ -89,4 +88,7 @@ class PackageList(ListModel): @pyqtProperty(bool, constant = True) def hasFooter(self) -> bool: + """ Indicating if the PackageList should have a Footer visible. For paginated PackageLists + :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise + """ return self._has_footer -- cgit v1.2.3 From f9f43b79b05525fe7028982cabd224fee2dde4c1 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 14:11:07 +0100 Subject: Don't pollute the package_info with section_title The previous implementation added a section_title to the package_info which was also stored in the packages.json. The section_title is now provided to the PackageModel as an extra optional argument. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 58 ++++++++++++++++----------------- plugins/Marketplace/PackageModel.py | 5 +-- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index aad1bbbaa1..121ac72308 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,8 +1,8 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtSlot, Qt from typing import Any, Dict, Generator, TYPE_CHECKING +from PyQt5.QtCore import pyqtSlot, Qt from UM.i18n import i18nCatalog @@ -30,11 +30,11 @@ class LocalPackageList(PackageList): "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), "material": catalog.i18nc("@label:property", "Bundled Materials") } - } # The section headers to be used for the different package categories + } # The section headers to be used for the different package categories def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) - self._application = CuraApplication.getInstance() + self._manager = CuraApplication.getInstance().getPackageManager() self._has_footer = False @pyqtSlot() @@ -50,52 +50,52 @@ class LocalPackageList(PackageList): def _getLocalPackages(self) -> None: """ Obtain the local packages. + The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages for the sections are sorted alphabetically on the display name """ sorted_sections = {} - # Filter the package list per section_title and sort these + # Filter the packages per section title and sort these alphabetically for section in self._getSections(): - packages = filter(lambda p: p["section_title"] == section, self._allPackageInfo()) - sorted_sections[section] = sorted(packages, key = lambda p: p["display_name"]) + packages = filter(lambda p: p.sectionTitle == section, self._allPackageInfo()) + sorted_sections[section] = sorted(packages, key = lambda p: p.displayName) - # Create a PackageModel from the sorted package_info and append them to the list + # Append the order PackageModels to the list for section in sorted_sections.values(): for package_data in section: - package = PackageModel(package_data, parent = self) - self.appendItem({"package": package}) + self.appendItem({"package": package_data}) self.setIsLoading(False) self.setHasMore(False) # All packages should have been loaded at this time - def _getSections(self) -> Generator[str]: + def _getSections(self) -> Generator[str, None, None]: """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the correct order""" for package_type in self.PACKAGE_SECTION_HEADER.values(): for section in package_type.values(): yield section - def _allPackageInfo(self) -> Generator[Dict[str, Any]]: - """ A generator which returns a unordered list of package_info, the section_title is appended to the each - package_info""" - - manager = self._application.getPackageManager() + def _allPackageInfo(self) -> Generator[PackageModel, None, None]: + """ A generator which returns a unordered list of all the PackageModels""" # Get all the installed packages, add a section_title depending on package_type and user installed - for package_type, packages in manager.getAllInstalledPackagesInfo().items(): - for package_data in packages: - bundled_or_installed = "installed" if manager.isUserInstalledPackage(package_data["package_id"]) else "bundled" - package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] - yield package_data + for packages in self._manager.getAllInstalledPackagesInfo().values(): + for package_info in packages: + yield self._makePackageModel(package_info) # Get all to be removed package_info's. These packages are still used in the current session so the user might - # to interact with these in the list - for package_data in manager.getPackagesToRemove().values(): - yield package_data["package_info"] - - for package_data in manager.getPackagesToInstall().values(): - package_info = package_data["package_info"] - package_type = package_info["package_type"] - package_info["section_title"] = self.PACKAGE_SECTION_HEADER["installed"][package_type] - yield package_info + # still want to interact with these. + for package_data in self._manager.getPackagesToRemove().values(): + yield self._makePackageModel(package_data["package_info"]) + + # Get all to be installed package_info's. Since the user might want to interact with these + for package_data in self._manager.getPackagesToInstall().values(): + yield self._makePackageModel(package_data["package_info"]) + + def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel: + """ Create a PackageModel from the package_info and determine its section_title""" + bundled_or_installed = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" + package_type = package_info["package_type"] + section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] + return PackageModel(package_info, section_title = section_title, parent = self) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 0ade18839e..e57a402fd6 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -16,16 +16,17 @@ class PackageModel(QObject): QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_data: Dict[str, Any], parent: QObject = None) -> None: + def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: """ Constructs a new model for a single package. :param package_data: The data received from the Marketplace API about the package to create. + :param section_title: If the packages are to be categorized per section provide the section_title :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) - self._section_title = package_data.get("section_title", None) + self._section_title = section_title @pyqtProperty(str, constant = True) def packageId(self) -> str: -- cgit v1.2.3 From 07fcf8b533d0b33968262d4f54b884bfbb259304 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 3 Nov 2021 16:01:43 +0100 Subject: Fixed missing qoutes :face_palm: Contributes to CURA-8558 --- 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 a2f3cf184c..bcdb02eb50 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -89,5 +89,5 @@ class PackageList(ListModel): @pyqtProperty(bool, constant = True) def hasFooter(self) -> bool: """ Indicating if the PackageList should have a Footer visible. For paginated PackageLists - :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise + :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise""" return self._has_footer -- cgit v1.2.3 From e7aecb6c06c278a1a5f29325d85a3fdaf4feba9c Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 3 Nov 2021 16:29:35 +0100 Subject: Fixed mypy typing failure @ghostkeeper being nerd snipped It's giving that typing failure because the section variable is re-used. First as elements from self._getSections (strs) and then as elements from sorted_sections.values() (List[PackageModel]s). Python has no variable scopes within functions so the variable still exists after the first for loop. Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 121ac72308..e2a13b051c 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -62,8 +62,8 @@ class LocalPackageList(PackageList): sorted_sections[section] = sorted(packages, key = lambda p: p.displayName) # Append the order PackageModels to the list - for section in sorted_sections.values(): - for package_data in section: + for sorted_section in sorted_sections.values(): + for package_data in sorted_section: self.appendItem({"package": package_data}) self.setIsLoading(False) -- cgit v1.2.3 From 3a94fc0ced1e846f492a5a25069893d272e0fd9a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 3 Nov 2021 17:58:16 +0100 Subject: Apply suggestions from code review Applied code review comments Co-authored-by: Jaime van Kessel --- plugins/Marketplace/LocalPackageList.py | 3 +-- plugins/Marketplace/PackageList.py | 2 +- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- plugins/Marketplace/resources/qml/Packages.qml | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index e2a13b051c..fad5430082 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -18,7 +18,6 @@ catalog = i18nCatalog("cura") class LocalPackageList(PackageList): - PackageRole = Qt.UserRole + 1 PACKAGE_SECTION_HEADER = { "installed": { @@ -46,7 +45,7 @@ class LocalPackageList(PackageList): self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) self._getLocalPackages() - self.setIsLoading(True) + self.setIsLoading(False) def _getLocalPackages(self) -> None: """ Obtain the local packages. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index bcdb02eb50..e3b490a834 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -42,7 +42,7 @@ class PackageList(ListModel): """ Resets and clears the list""" self.clear() - isLoadingChanged = pyqtSignal() # The signal for isLoading property + isLoadingChanged = pyqtSignal() def setIsLoading(self, value: bool) -> None: if self._is_loading != value: diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 2d95904257..4f36de01d0 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -66,7 +66,7 @@ Window font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") - text: "" + text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") } } diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 8ab241e784..c9ddf88d16 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -25,11 +25,10 @@ ScrollView spacing: UM.Theme.getSize("default_margin").height section.property: "package.sectionTitle" - section.criteria: ViewSection.FullString section.delegate: Rectangle { width: packagesListview.width - height: sectionHeaderText.implicitHeight + UM.Theme.getSize("default_margin").height + height: sectionHeaderText.height + UM.Theme.getSize("default_margin").height color: UM.Theme.getColor("detail_background") -- cgit v1.2.3 From e01e47b8fa1841401a18535827cd26d7c3e4a09c Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 08:11:50 +0100 Subject: Performance increase for obtaining LocalPackages The speed increase on the function when running Yappi `LocalPackageList.updatePackages` | original | now | |----------|------| | 14 ms | 4 ms | Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index fad5430082..fd0e340c86 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,8 +1,8 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict, Generator, TYPE_CHECKING -from PyQt5.QtCore import pyqtSlot, Qt +from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING +from PyQt5.QtCore import pyqtSlot, QObject from UM.i18n import i18nCatalog @@ -11,9 +11,6 @@ from cura.CuraApplication import CuraApplication from .PackageList import PackageList from .PackageModel import PackageModel # The contents of this list. -if TYPE_CHECKING: - from PyQt5.QtCore import QObject - catalog = i18nCatalog("cura") @@ -31,7 +28,7 @@ class LocalPackageList(PackageList): } } # The section headers to be used for the different package categories - def __init__(self, parent: "QObject" = None) -> None: + def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) self._manager = CuraApplication.getInstance().getPackageManager() self._has_footer = False @@ -51,22 +48,14 @@ class LocalPackageList(PackageList): """ Obtain the local packages. The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages - for the sections are sorted alphabetically on the display name + for the sections are sorted alphabetically on the display name. These sorted sections are then added to the items """ - - sorted_sections = {} - # Filter the packages per section title and sort these alphabetically + package_info = list(self._allPackageInfo()) + sorted_sections: List[Dict[str, PackageModel]] = [] for section in self._getSections(): - packages = filter(lambda p: p.sectionTitle == section, self._allPackageInfo()) - sorted_sections[section] = sorted(packages, key = lambda p: p.displayName) - - # Append the order PackageModels to the list - for sorted_section in sorted_sections.values(): - for package_data in sorted_section: - self.appendItem({"package": package_data}) - - self.setIsLoading(False) - self.setHasMore(False) # All packages should have been loaded at this time + packages = filter(lambda p: p.sectionTitle == section, package_info) + sorted_sections.extend([{"package": p} for p in sorted(packages, key = lambda p: p.displayName)]) + self.setItems(sorted_sections) def _getSections(self) -> Generator[str, None, None]: """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the -- cgit v1.2.3 From 11b3b081988691b749e194648a2b3d2af867f280 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 08:19:05 +0100 Subject: Implemented code review suggestions Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 6 +++++- plugins/Marketplace/PackageList.py | 8 ++++---- plugins/Marketplace/RemotePackageList.py | 3 +-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index fd0e340c86..6acbaa8500 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -4,6 +4,9 @@ from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING from PyQt5.QtCore import pyqtSlot, QObject +if TYPE_CHECKING: + from PyQt5.QtCore import QObject + from UM.i18n import i18nCatalog from cura.CuraApplication import CuraApplication @@ -28,7 +31,7 @@ class LocalPackageList(PackageList): } } # The section headers to be used for the different package categories - def __init__(self, parent: Optional[QObject] = None) -> None: + def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._manager = CuraApplication.getInstance().getPackageManager() self._has_footer = False @@ -43,6 +46,7 @@ class LocalPackageList(PackageList): self.setIsLoading(True) self._getLocalPackages() self.setIsLoading(False) + self.setHasMore(False) # All packages should have been loaded at this time def _getLocalPackages(self) -> None: """ Obtain the local packages. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index e3b490a834..8171d168f2 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel @@ -19,7 +19,7 @@ class PackageList(ListModel): """ PackageRole = Qt.UserRole + 1 - def __init__(self, parent: "QObject" = None) -> None: + def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._error_message = "" self.addRoleName(self.PackageRole, "package") @@ -56,7 +56,7 @@ class PackageList(ListModel): """ return self._is_loading - hasMoreChanged = pyqtSignal() # The signal for hasMore property + hasMoreChanged = pyqtSignal() def setHasMore(self, value: bool) -> None: if self._has_more != value: @@ -70,7 +70,7 @@ class PackageList(ListModel): """ return self._has_more - errorMessageChanged = pyqtSignal() # The signal for errorMessage property + errorMessageChanged = pyqtSignal() def setErrorMessage(self, error_message: str) -> None: if self._error_message != error_message: diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 3cfb11d6ba..7e8ee321ac 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -18,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") @@ -26,7 +25,7 @@ catalog = i18nCatalog("cura") class RemotePackageList(PackageList): ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. - def __init__(self, parent: "QObject" = None) -> None: + def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._ongoing_request: Optional[HttpRequestData] = None -- cgit v1.2.3 From a58891ce58ecf02631efbe32f8ec3ddf7f65ee55 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 08:22:58 +0100 Subject: Fixed the loading spinner not spinning at first construction Contributes to CURA-8558 --- plugins/Marketplace/RemotePackageList.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 7e8ee321ac..3a6a329a52 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -33,6 +33,7 @@ class RemotePackageList(PackageList): self._package_type_filter = "" self._request_url = self._initialRequestUrl() + self.isLoadingChanged.emit() def __del__(self) -> None: """ -- cgit v1.2.3 From cbf83e500d15429e9d6e5ae148cc7873cba98351 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 10:04:59 +0100 Subject: Changed behaviour of hoover over button Per request of UX Contributes to CURA-8558 --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index e6c1406858..bd857510f3 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -14,11 +14,13 @@ Button height: childrenRect.height hoverEnabled: true + property color borderColor: hovered ? UM.Theme.getColor("primary") : "transparent" + property color backgroundColor: hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button") background: Rectangle { - color: UM.Theme.getColor("action_button") - border.color: "transparent" + color: backgroundColor + border.color: borderColor border.width: UM.Theme.getSize("default_lining").width } -- cgit v1.2.3 From fd409215c4df80e46c9ca7a2c9f4d59b6ae6b814 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 10:05:35 +0100 Subject: Tooltip shows point Per UX request Contributes to CURA-8558 --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index bd857510f3..4a734f45ba 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -29,7 +29,6 @@ Button id: tooltip tooltipText: catalog.i18nc("@info:tooltip", "Manage packages") - arrowSize: 0 visible: root.hovered } -- cgit v1.2.3 From a0467cd66f665d8c0adb684d40c81b9464b02486 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 4 Nov 2021 10:39:00 +0100 Subject: Fixed hard crash when deconstructing RemotePackageList while parsing Contributes to CURA-8558 --- plugins/Marketplace/RemotePackageList.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 3a6a329a52..8fa75453c1 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -107,8 +107,14 @@ class RemotePackageList(PackageList): return for package_data in response_data["data"]: - package = PackageModel(package_data, parent = self) - self.appendItem({"package": package}) # Add it to this list model. + try: + package = PackageModel(package_data, parent = self) + 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 + # between de-/constructing RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object + # was deleted when it was still parsing the response + return self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self._ongoing_request = None -- cgit v1.2.3 From e93ecd369991dd2f64f89c17326390bf967189c6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 5 Nov 2021 09:26:27 +0100 Subject: Move what is already there of 'package card' to it's own file. part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 28 +++++++++++++++++++++++ plugins/Marketplace/resources/qml/Packages.qml | 19 ++------------- 2 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackageCard.qml diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml new file mode 100644 index 0000000000..0a6c33a5b9 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -0,0 +1,28 @@ +// 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 + +Rectangle +{ + property var packageData + + width: parent.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: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + } +} diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index c9ddf88d16..95064c4469 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -46,24 +46,9 @@ ScrollView } } - delegate: Rectangle + delegate: PackageCard { - width: packagesListview.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") - } + packageData: model.package } //Wrapper item to add spacing between content and footer. -- cgit v1.2.3 From 2cdda695e9c8294c32a4fdea731702de61e2417d Mon Sep 17 00:00:00 2001 From: Lorenzo Romagnoli Date: Fri, 5 Nov 2021 10:15:35 +0100 Subject: adjusted style of hover and button sizes (#10739) adjusted style of hover and button sizes Co-authored-by: Jaime van Kessel --- .../Marketplace/resources/qml/ManagePackagesButton.qml | 15 ++++++++------- plugins/Marketplace/resources/qml/Marketplace.qml | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 4a734f45ba..31b97d89ed 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -10,18 +10,17 @@ import QtQuick.Controls 2.15 Button { id: root - width: childrenRect.width - height: childrenRect.height - + width: UM.Theme.getSize("button_icon").width + height: UM.Theme.getSize("button_icon").height hoverEnabled: true - property color borderColor: hovered ? UM.Theme.getColor("primary") : "transparent" property color backgroundColor: hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button") background: Rectangle { color: backgroundColor - border.color: borderColor - border.width: UM.Theme.getSize("default_lining").width + border.color: transparent + radius: Math.round(width * 0.5) + } Cura.ToolTip @@ -38,8 +37,10 @@ Button width: UM.Theme.getSize("section_icon").width height: UM.Theme.getSize("section_icon").height - + color: UM.Theme.getColor("icon") source: UM.Theme.getIcon("Settings") + anchors.centerIn: parent + } } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 4f36de01d0..430c237252 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -94,18 +94,20 @@ Window id: pageSelectionTabBar anchors.right: managePackagesButton.left anchors.rightMargin: UM.Theme.getSize("default_margin").width - + height: UM.Theme.getSize("button_icon").height spacing: 0 PackageTypeTab { width: implicitWidth + padding: UM.Theme.getSize("default_margin").width/2 text: catalog.i18nc("@button", "Plugins") onClicked: content.source = "Plugins.qml" } PackageTypeTab { width: implicitWidth + padding: Math.round(UM.Theme.getSize("default_margin").width / 2) text: catalog.i18nc("@button", "Materials") onClicked: content.source = "Materials.qml" } -- cgit v1.2.3 From bb51dc7d140d6451e60fe4987c9e52682622916f Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 5 Nov 2021 18:44:31 +0100 Subject: Gather and show required information. Also add 'Downalod' icon. Still very much WIP and nonfunctional. part of CURA-8561 --- plugins/Marketplace/PackageModel.py | 52 +++- plugins/Marketplace/resources/qml/PackageCard.qml | 315 ++++++++++++++++++++- .../themes/cura-light/icons/default/Download.svg | 3 + 3 files changed, 361 insertions(+), 9 deletions(-) create mode 100644 resources/themes/cura-light/icons/default/Download.svg diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index e57a402fd6..0e73cac3b7 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -4,10 +4,12 @@ from PyQt5.QtCore import pyqtProperty, QObject from typing import Any, Dict, Optional -from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. +from UM.Util import parseBool +from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. catalog = i18nCatalog("cura") + class PackageModel(QObject): """ Represents a package, containing all the relevant information to be displayed about a package. @@ -25,17 +27,65 @@ class PackageModel(QObject): """ super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") + + self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) + self._is_verified = "verified" in package_data.get("tags", []) + self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. + self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. + self._download_count = package_data.get("download_count", 0) + self._description = package_data.get("description", "") + + self._download_url = package_data.get("download_url", "") # Not used yet, will be. + self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? + + author_data = package_data.get("author", {}) + self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) + self._author_info_url = author_data.get("website", "") + self._section_title = section_title + # Note that there's a lot more info in the package_data than just these specified here. @pyqtProperty(str, constant = True) def packageId(self) -> str: return self._package_id + @pyqtProperty(str, constant=True) + def iconUrl(self): + return self._icon_url + @pyqtProperty(str, constant = True) def displayName(self) -> str: return self._display_name + @pyqtProperty(bool, constant=True) + def isVerified(self): + return self._is_verified + + @pyqtProperty(str, constant=True) + def packageVersion(self): + return self._package_version + + @pyqtProperty(str, constant=True) + def packageInfoUrl(self): + return self._package_info_url + + @pyqtProperty(int, constant=True) + def downloadCount(self): + return self._download_count + + @pyqtProperty(str, constant=True) + def description(self): + return self._description + + @pyqtProperty(str, constant=True) + def authorName(self): + return self._author_name + + @pyqtProperty(str, constant=True) + def authorInfoUrl(self): + return self._author_info_url + @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 0a6c33a5b9..c200910077 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -3,7 +3,9 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 -import UM 1.4 as UM + +import UM 1.6 as UM +import Cura 1.6 as Cura Rectangle { @@ -15,14 +17,311 @@ Rectangle color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width - Label + Image + { + id: packageIcon + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("section_icon").width * 3 + height: UM.Theme.getSize("section_icon").height * 3 + + source: packageData.iconUrl != "" ? packageData.iconUrl : UM.Theme.getImage("CicleOutline") + } + + Item { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: Math.round((parent.height - height) / 2) + anchors + { + top: parent.top + bottom: parent.bottom + right: parent.right + left: packageIcon.left + margins: UM.Theme.getSize("thin_margin").width + } + + Item + { + id: firstRowItems + anchors + { + top: parent.top + right: parent.right + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + + Label + { + id: titleLabel + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + + text: packageData.displayName + } + + UM.RecolorImage + { + id: verifiedIcon + anchors + { + top: parent.top + left: titleLabel.right + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("section_icon").height + height: UM.Theme.getSize("section_icon").height + color: UM.Theme.getColor("icon") + + visible: packageData.isVerified + source: UM.Theme.getIcon("CheckCircle") + + // TODO: on hover + } + + Rectangle + { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card + id: certifiedIcon + anchors + { + top: parent.top + left: verifiedIcon.right + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("section_icon").height + height: UM.Theme.getSize("section_icon").height + + // TODO: on hover + } + + Label + { + id: versionLabel + anchors + { + top: parent.top + left: certifiedIcon.right + margins: UM.Theme.getSize("thin_margin").width + } + + text: packageData.packageVersion + } + + UM.RecolorImage + { + id: packageInfoLink + anchors + { + top: parent.top + right: parent.right + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("section_icon").height + height: UM.Theme.getSize("section_icon").height + color: UM.Theme.getColor("icon") + + source: UM.Theme.getIcon("Link") + + // TODO: on clicked url + } + } + + Item + { + id: secondRowItems + anchors + { + top: firstRowItems.bottom + right: parent.right + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + + UM.RecolorImage + { + id: downloadCountIcon + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("section_icon").height + height: UM.Theme.getSize("section_icon").height + color: UM.Theme.getColor("icon") + + source: UM.Theme.getIcon("Download") // TODO: The right icon. + } + + Label + { + id: downloadCountLobel + anchors + { + top: parent.top + left: downloadCountIcon.right + margins: UM.Theme.getSize("thin_margin").width + } + + text: packageData.downloadCount + } + } + + Item + { + id: mainRowItems + anchors + { + top: secondRowItems.bottom + bottom: footerRowItems.top + right: parent.right + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + + readonly property int charLimitSmall: 130 + + Label + { + id: descriptionLabel + anchors + { + top: parent.top + left: parent.left + right: parent.right + bottom: parent.bottom + margins: UM.Theme.getSize("thin_margin").width + } + + maximumLineCount: 2 + text: packageData.description.substring(0, parent.charLimitSmall) + } + + Cura.TertiaryButton + { + id: readMoreLabel + anchors + { + right: parent.right + bottom: parent.bottom + margins: UM.Theme.getSize("thin_margin").width + } + + visible: descriptionLabel.text.length > parent.charLimitSmall + text: catalog.i18nc("@info", "Read more") + + // TODO: overlaps elided text, is this ok? + } + + // TODO: _only_ limit to 130 or 2 rows (& all that that entails) when 'small' + } + + Item + { + id: footerRowItems + anchors + { + bottom: parent.bottom + right: parent.right + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + + Item + { + Label + { + id: preAuthorNameText + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + + text: catalog.i18nc("@label", "By") + } + + Cura.TertiaryButton + { + id: authorNameLabel + anchors + { + top: parent.top + left: preAuthorNameText.right + margins: UM.Theme.getSize("thin_margin").width + } + + text: packageData.authorName + + // TODO on clicked (is link) -> MouseArea? + } + } + + Item + { + id: packageActionButtons + anchors + { + bottom: parent.bottom + right: parent.right + margins: UM.Theme.getSize("thin_margin").width + } + + Cura.SecondaryButton + { + id: disableButton + anchors + { + right: uninstallButton.left + bottom: parent.bottom + margins: UM.Theme.getSize("thin_margin").width + } + height: parent.height + + text: catalog.i18nc("@button", "Disable") + // not functional right now + } + + Cura.SecondaryButton + { + id: uninstallButton + anchors + { + right: updateButton.left + bottom: parent.bottom + margins: UM.Theme.getSize("thin_margin").width + } + height: parent.height + + text: catalog.i18nc("@button", "Uninstall") + // not functional right now + } + + Cura.PrimaryButton + { + id: updateButton + anchors + { + right: parent.right + bottom: parent.bottom + margins: UM.Theme.getSize("thin_margin").width + } + height: parent.height - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") + text: catalog.i18nc("@button", "Update") + // not functional right now + } + } + } } } diff --git a/resources/themes/cura-light/icons/default/Download.svg b/resources/themes/cura-light/icons/default/Download.svg new file mode 100644 index 0000000000..cbe0da2a99 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Download.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From e0508b0f4f1fd4d0b705f70097e717cb9d03248a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 15:08:33 +0100 Subject: Correct size and margins for package icon Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 6 +++--- resources/themes/cura-light/theme.json | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index c200910077..9925801abc 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -24,10 +24,10 @@ Rectangle { top: parent.top left: parent.left - margins: UM.Theme.getSize("thin_margin").width + margins: UM.Theme.getSize("default_margin").width } - width: UM.Theme.getSize("section_icon").width * 3 - height: UM.Theme.getSize("section_icon").height * 3 + width: UM.Theme.getSize("card_icon").width + height: UM.Theme.getSize("card_icon").height source: packageData.iconUrl != "" ? packageData.iconUrl : UM.Theme.getImage("CicleOutline") } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index abd4844f47..07ed1a899d 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -553,7 +553,9 @@ "standard_list_lineheight": [1.5, 1.5], "standard_arrow": [1.0, 1.0], + "card": [25.0, 8.75], + "card_icon": [6.0, 6.0], "button": [4, 4], "button_icon": [2.5, 2.5], -- cgit v1.2.3 From 4014562cdcebdcdef81503f774423efc79f57165 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 15:47:09 +0100 Subject: Use rows and columns instead of anchors for layout This is in my opinion much easier to follow and maintain. It also fixes the layout. The original code had a lot of overlapping parts. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 289 +++++----------------- 1 file changed, 58 insertions(+), 231 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 9925801abc..88eeb50356 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -3,6 +3,7 @@ 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 @@ -12,312 +13,138 @@ Rectangle property var packageData width: parent.width - height: UM.Theme.getSize("card").height + height: childrenRect.height + UM.Theme.getSize("default_margin").height * 2 color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width - Image + RowLayout { - id: packageIcon - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - width: UM.Theme.getSize("card_icon").width - height: UM.Theme.getSize("card_icon").height + width: parent.width - UM.Theme.getSize("default_margin").width * 2 + height: childrenRect.height + x: UM.Theme.getSize("default_margin").width + y: UM.Theme.getSize("default_margin").height - source: packageData.iconUrl != "" ? packageData.iconUrl : UM.Theme.getImage("CicleOutline") - } + spacing: UM.Theme.getSize("default_margin").width - Item - { - anchors + Image //Separate column for icon on the left. { - top: parent.top - bottom: parent.bottom - right: parent.right - left: packageIcon.left - margins: UM.Theme.getSize("thin_margin").width + Layout.preferredWidth: UM.Theme.getSize("card_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_icon").height + + source: packageData.iconUrl != "" ? packageData.iconUrl : UM.Theme.getImage("CicleOutline") } - Item + Column { - id: firstRowItems - anchors - { - top: parent.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height - Label - { - id: titleLabel - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - - text: packageData.displayName - } + spacing: UM.Theme.getSize("default_margin").height - UM.RecolorImage + RowLayout //Title row. { - id: verifiedIcon - anchors - { - top: parent.top - left: titleLabel.right - margins: UM.Theme.getSize("thin_margin").width - } - width: UM.Theme.getSize("section_icon").height - height: UM.Theme.getSize("section_icon").height - color: UM.Theme.getColor("icon") + width: parent.width - visible: packageData.isVerified - source: UM.Theme.getIcon("CheckCircle") - - // TODO: on hover - } + spacing: UM.Theme.getSize("default_margin").width - Rectangle - { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card - id: certifiedIcon - anchors + Label { - top: parent.top - left: verifiedIcon.right - margins: UM.Theme.getSize("thin_margin").width - } - width: UM.Theme.getSize("section_icon").height - height: UM.Theme.getSize("section_icon").height + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") - // TODO: on hover - } + text: packageData.displayName + } - Label - { - id: versionLabel - anchors + UM.RecolorImage { - top: parent.top - left: certifiedIcon.right - margins: UM.Theme.getSize("thin_margin").width - } + Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 + Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 - text: packageData.packageVersion - } + color: UM.Theme.getColor("icon") + visible: packageData.isVerified + source: UM.Theme.getIcon("CheckCircle") - UM.RecolorImage - { - id: packageInfoLink - anchors - { - top: parent.top - right: parent.right - margins: UM.Theme.getSize("thin_margin").width + // TODO: on hover } - width: UM.Theme.getSize("section_icon").height - height: UM.Theme.getSize("section_icon").height - color: UM.Theme.getColor("icon") - - source: UM.Theme.getIcon("Link") - // TODO: on clicked url - } - } + Rectangle + { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card + Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 + Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 - Item - { - id: secondRowItems - anchors - { - top: firstRowItems.bottom - right: parent.right - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } + // TODO: on hover + } - UM.RecolorImage - { - id: downloadCountIcon - anchors + Label { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } - width: UM.Theme.getSize("section_icon").height - height: UM.Theme.getSize("section_icon").height - color: UM.Theme.getColor("icon") + Layout.fillWidth: true - source: UM.Theme.getIcon("Download") // TODO: The right icon. - } + text: packageData.packageVersion + } - Label - { - id: downloadCountLobel - anchors + UM.RecolorImage { - top: parent.top - left: downloadCountIcon.right - margins: UM.Theme.getSize("thin_margin").width - } + Layout.preferredWidth: UM.Theme.getSize("section_icon").width + Layout.preferredHeight: UM.Theme.getSize("section_icon").height - text: packageData.downloadCount - } - } + color: UM.Theme.getColor("icon") + source: UM.Theme.getIcon("Link") - Item - { - id: mainRowItems - anchors - { - top: secondRowItems.bottom - bottom: footerRowItems.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("thin_margin").width + // TODO: on clicked url + } } - readonly property int charLimitSmall: 130 - Label { - id: descriptionLabel - anchors - { - top: parent.top - left: parent.left - right: parent.right - bottom: parent.bottom - margins: UM.Theme.getSize("thin_margin").width - } + width: parent.width maximumLineCount: 2 - text: packageData.description.substring(0, parent.charLimitSmall) + text: packageData.description + elide: Text.ElideRight //TODO: Make space for Read More button. } - - Cura.TertiaryButton + /*Cura.TertiaryButton { - id: readMoreLabel - anchors - { - right: parent.right - bottom: parent.bottom - margins: UM.Theme.getSize("thin_margin").width - } + //TODO: Inline in description. visible: descriptionLabel.text.length > parent.charLimitSmall text: catalog.i18nc("@info", "Read more") // TODO: overlaps elided text, is this ok? - } - - // TODO: _only_ limit to 130 or 2 rows (& all that that entails) when 'small' - } + }*/ - Item - { - id: footerRowItems - anchors + RowLayout //Author and action buttons. { - bottom: parent.bottom - right: parent.right - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } + width: parent.width - Item - { Label { - id: preAuthorNameText - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("thin_margin").width - } - text: catalog.i18nc("@label", "By") } Cura.TertiaryButton { - id: authorNameLabel - anchors - { - top: parent.top - left: preAuthorNameText.right - margins: UM.Theme.getSize("thin_margin").width - } + Layout.fillWidth: true text: packageData.authorName // TODO on clicked (is link) -> MouseArea? } - } - - Item - { - id: packageActionButtons - anchors - { - bottom: parent.bottom - right: parent.right - margins: UM.Theme.getSize("thin_margin").width - } Cura.SecondaryButton { - id: disableButton - anchors - { - right: uninstallButton.left - bottom: parent.bottom - margins: UM.Theme.getSize("thin_margin").width - } - height: parent.height - text: catalog.i18nc("@button", "Disable") // not functional right now } Cura.SecondaryButton { - id: uninstallButton - anchors - { - right: updateButton.left - bottom: parent.bottom - margins: UM.Theme.getSize("thin_margin").width - } - height: parent.height - text: catalog.i18nc("@button", "Uninstall") // not functional right now } Cura.PrimaryButton { - id: updateButton - anchors - { - right: parent.right - bottom: parent.bottom - margins: UM.Theme.getSize("thin_margin").width - } - height: parent.height - text: catalog.i18nc("@button", "Update") // not functional right now } -- cgit v1.2.3 From 2ce31d0e71390e63986b0692d90132a3ecb5a644 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 15:51:34 +0100 Subject: Add placeholder image The 'CicleOutline' image doesn't exist. There is no design for this image so I'm adding the placeholder that the previous Marketplace had. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/images/placeholder.svg | 3 +++ plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 plugins/Marketplace/resources/images/placeholder.svg diff --git a/plugins/Marketplace/resources/images/placeholder.svg b/plugins/Marketplace/resources/images/placeholder.svg new file mode 100644 index 0000000000..cc674a4b38 --- /dev/null +++ b/plugins/Marketplace/resources/images/placeholder.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 88eeb50356..52e11ebff9 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -32,7 +32,7 @@ Rectangle Layout.preferredWidth: UM.Theme.getSize("card_icon").width Layout.preferredHeight: UM.Theme.getSize("card_icon").height - source: packageData.iconUrl != "" ? packageData.iconUrl : UM.Theme.getImage("CicleOutline") + source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } Column -- cgit v1.2.3 From abe834752387bca92a2897344b470a537dbe26b7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 15:54:42 +0100 Subject: Fix QML warning about not having parents It seems that the ListView doesn't always set the parent element correctly if it's not yet in view. This is a workaround that seems to work fine to remove the QML warnings about parent not being defined. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 52e11ebff9..36a99410fe 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,7 @@ Rectangle { property var packageData - width: parent.width + width: parent ? parent.width : 0 height: childrenRect.height + UM.Theme.getSize("default_margin").height * 2 color: UM.Theme.getColor("main_background") -- cgit v1.2.3 From 8c086b9fd73e0035e1b70191c78bb91c0fe550fb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 15:58:37 +0100 Subject: Align everything to top This seems to be the alignment in the design. Also gets rid of binding loops because we automatically adjust the height so you can't align to the centre or the bottom then. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 36a99410fe..0fd2cb9ac1 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -31,6 +31,7 @@ Rectangle { Layout.preferredWidth: UM.Theme.getSize("card_icon").width Layout.preferredHeight: UM.Theme.getSize("card_icon").height + Layout.alignment: Qt.AlignTop source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } @@ -39,6 +40,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height + Layout.alignment: Qt.AlignTop spacing: UM.Theme.getSize("default_margin").height @@ -50,6 +52,8 @@ Rectangle Label { + Layout.alignment: Qt.AlignTop + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") @@ -60,6 +64,7 @@ Rectangle { Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 + Layout.alignment: Qt.AlignTop color: UM.Theme.getColor("icon") visible: packageData.isVerified @@ -72,6 +77,7 @@ Rectangle { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 + Layout.alignment: Qt.AlignTop // TODO: on hover } @@ -79,6 +85,7 @@ Rectangle Label { Layout.fillWidth: true + Layout.alignment: Qt.AlignTop text: packageData.packageVersion } @@ -87,6 +94,7 @@ Rectangle { Layout.preferredWidth: UM.Theme.getSize("section_icon").width Layout.preferredHeight: UM.Theme.getSize("section_icon").height + Layout.alignment: Qt.AlignTop color: UM.Theme.getColor("icon") source: UM.Theme.getIcon("Link") @@ -119,12 +127,15 @@ Rectangle Label { + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@label", "By") } Cura.TertiaryButton { Layout.fillWidth: true + Layout.alignment: Qt.AlignTop text: packageData.authorName -- cgit v1.2.3 From 468c2b89e196b5a92ff1a702dded52d187bb8746 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 16:09:58 +0100 Subject: Use wrapping for package description Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 0fd2cb9ac1..cb7f4edc54 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -107,8 +107,9 @@ Rectangle { width: parent.width - maximumLineCount: 2 text: packageData.description + maximumLineCount: 2 + wrapMode: Text.Wrap elide: Text.ElideRight //TODO: Make space for Read More button. } /*Cura.TertiaryButton -- cgit v1.2.3 From c8741898bf2aa519d3d0203f75a2ca661a7628fd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 16:28:12 +0100 Subject: Decent attempt at inlining Read More button Not perfect yet. The elide is missing, for one. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 47 ++++++++++++++++------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index cb7f4edc54..15bb6bcb83 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -103,24 +103,44 @@ Rectangle } } - Label + Item { width: parent.width + height: descriptionLabel.height - text: packageData.description - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight //TODO: Make space for Read More button. - } - /*Cura.TertiaryButton - { - //TODO: Inline in description. + Label + { + id: descriptionLabel + width: parent.width + + text: packageData.description + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: + { + if(line.isLast) + { + line.width = Math.min(line.width, parent.width - readMoreButton.width) + } + } + } - visible: descriptionLabel.text.length > parent.charLimitSmall - text: catalog.i18nc("@info", "Read more") + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: parent.bottom + height: authorBy.height //Height of a single line. + + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: 0 + textFont: descriptionLabel.font - // TODO: overlaps elided text, is this ok? - }*/ + text: catalog.i18nc("@info", "Read more") + } + } RowLayout //Author and action buttons. { @@ -128,6 +148,7 @@ Rectangle Label { + id: authorBy Layout.alignment: Qt.AlignTop text: catalog.i18nc("@label", "By") -- cgit v1.2.3 From 57093f0ef6d003b1ed1757dc99a6d0504e63c13b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 16:34:21 +0100 Subject: Hide Read More button if not truncated There would be nothing to read. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 15bb6bcb83..4b1ab78c72 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -120,9 +120,9 @@ Rectangle onLineLaidOut: { - if(line.isLast) + if(truncated && line.isLast) { - line.width = Math.min(line.width, parent.width - readMoreButton.width) + line.width = Math.min(line.width, parent.width - readMoreButton.width); } } } @@ -134,6 +134,8 @@ Rectangle anchors.bottom: parent.bottom height: authorBy.height //Height of a single line. + visible: descriptionLabel.truncated + enabled: visible leftPadding: UM.Theme.getSize("default_margin").width rightPadding: 0 textFont: descriptionLabel.font -- cgit v1.2.3 From 7b7cb43b021bed388d7f7edc910419540bdec518 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 16:58:30 +0100 Subject: Improved elision It seems to correctly place the elide character now. One more detail that's incorrect is that it shows two elision characters if it's eliding due to maximum line count. I'll see what I can do... Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 4b1ab78c72..ee35e6c5a0 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -112,6 +112,7 @@ Rectangle { id: descriptionLabel width: parent.width + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description maximumLineCount: 2 @@ -122,7 +123,9 @@ Rectangle { if(truncated && line.isLast) { - line.width = Math.min(line.width, parent.width - readMoreButton.width); + line.width = Math.min(line.implicitWidth, + parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ")); + descriptionLabel.lastLineWidth = line.implicitWidth; } } } @@ -132,15 +135,26 @@ Rectangle id: readMoreButton anchors.right: parent.right anchors.bottom: parent.bottom - height: authorBy.height //Height of a single line. + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") visible: descriptionLabel.truncated enabled: visible leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: 0 + rightPadding: UM.Theme.getSize("wide_margin").width textFont: descriptionLabel.font + isIconOnRightSide: true + } - text: catalog.i18nc("@info", "Read more") + Label + { + text: "... " + visible: descriptionLabel.truncated + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: readMoreButton.bottom } } @@ -186,4 +200,10 @@ Rectangle } } } + + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } } -- cgit v1.2.3 From 5a698bd91fafbd2bd4b95ced7c9f4e8628fd9f23 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:20:37 +0100 Subject: Truncate double ellipsis where possible I couldn't get it to truncate it if the double ellipsis is the only text on the line, like if the description contains a white line and more than 2 lines in total. It then looks like a double ellipsis (6 dots instead of 3). Doesn't look the worst, but a bit strange, but it's really difficult to fix. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index ee35e6c5a0..d9a99c44d0 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -123,8 +123,15 @@ Rectangle { if(truncated && line.isLast) { - line.width = Math.min(line.implicitWidth, - parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ")); + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… "); + if(line.implicitWidth > max_line_width) + { + line.width = max_line_width; + } + else + { + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. + } descriptionLabel.lastLineWidth = line.implicitWidth; } } @@ -150,7 +157,7 @@ Rectangle Label { - text: "... " + text: "… " visible: descriptionLabel.truncated anchors.left: parent.left anchors.leftMargin: descriptionLabel.lastLineWidth -- cgit v1.2.3 From c56240f2766505f03f24b295641f2060acade6e2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:22:01 +0100 Subject: Use correct icon for external links Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index d9a99c44d0..fa375edcb6 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -97,7 +97,7 @@ Rectangle Layout.alignment: Qt.AlignTop color: UM.Theme.getColor("icon") - source: UM.Theme.getIcon("Link") + source: UM.Theme.getIcon("LinkExternal") // TODO: on clicked url } -- cgit v1.2.3 From 234475547eb97bcce9b2b3826351df4748fc0748 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:25:02 +0100 Subject: Use correct font for all text elements Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index fa375edcb6..0e24cbcaa7 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -54,10 +54,9 @@ Rectangle { Layout.alignment: Qt.AlignTop + text: packageData.displayName font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") - - text: packageData.displayName } UM.RecolorImage @@ -88,6 +87,7 @@ Rectangle Layout.alignment: Qt.AlignTop text: packageData.packageVersion + font: UM.Theme.getFont("small") } UM.RecolorImage @@ -115,6 +115,7 @@ Rectangle property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description + font: UM.Theme.getFont("default") maximumLineCount: 2 wrapMode: Text.Wrap elide: Text.ElideRight @@ -157,11 +158,13 @@ Rectangle Label { - text: "… " - visible: descriptionLabel.truncated anchors.left: parent.left anchors.leftMargin: descriptionLabel.lastLineWidth anchors.bottom: readMoreButton.bottom + + text: "… " + font: descriptionLabel.font + visible: descriptionLabel.truncated } } @@ -175,6 +178,7 @@ Rectangle Layout.alignment: Qt.AlignTop text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") } Cura.TertiaryButton @@ -183,6 +187,7 @@ Rectangle Layout.alignment: Qt.AlignTop text: packageData.authorName + textFont: UM.Theme.getFont("default") // TODO on clicked (is link) -> MouseArea? } -- cgit v1.2.3 From 4119cf420970b2fee440347f92dedf86a1f196e9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:30:52 +0100 Subject: Fix layout of author button Bold, correct position and add the icon. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 0e24cbcaa7..0c5f133dfb 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -172,6 +172,8 @@ Rectangle { width: parent.width + spacing: UM.Theme.getSize("default_margin").width + Label { id: authorBy @@ -184,10 +186,15 @@ Rectangle Cura.TertiaryButton { Layout.fillWidth: true + Layout.preferredHeight: authorBy.height Layout.alignment: Qt.AlignTop text: packageData.authorName - textFont: UM.Theme.getFont("default") + textFont: UM.Theme.getFont("default_bold") + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true // TODO on clicked (is link) -> MouseArea? } -- cgit v1.2.3 From f498952830803c04063040dcda2ebfbc2c5e9397 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:32:22 +0100 Subject: Give text colours to text This way it's still visible in the dark theme, rather than black on black. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 0c5f133dfb..61632de3ef 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -88,6 +88,7 @@ Rectangle text: packageData.packageVersion font: UM.Theme.getFont("small") + color: UM.Theme.getColor("text") } UM.RecolorImage @@ -116,6 +117,7 @@ Rectangle text: packageData.description font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") maximumLineCount: 2 wrapMode: Text.Wrap elide: Text.ElideRight @@ -164,6 +166,7 @@ Rectangle text: "… " font: descriptionLabel.font + color: descriptionLabel.color visible: descriptionLabel.truncated } } @@ -181,6 +184,7 @@ Rectangle text: catalog.i18nc("@label", "By") font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") } Cura.TertiaryButton -- cgit v1.2.3 From d526e3be8cca3e2240608a8ad6350f88327a63f8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:40:10 +0100 Subject: Easier layout shifting when icons are invisible The Row element automatically hides them and removes any spacing if they are invisible. Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 61632de3ef..ab26699ce8 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -59,26 +59,31 @@ Rectangle color: UM.Theme.getColor("text") } - UM.RecolorImage + Row //Row inside row, but the non-layout version skips invisible elements. { - Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 - Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 + spacing: parent.spacing Layout.alignment: Qt.AlignTop - color: UM.Theme.getColor("icon") - visible: packageData.isVerified - source: UM.Theme.getIcon("CheckCircle") + UM.RecolorImage + { + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height - // TODO: on hover - } + color: UM.Theme.getColor("icon") + visible: packageData.isVerified + source: UM.Theme.getIcon("CheckCircle") - Rectangle - { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card - Layout.preferredWidth: visible ? UM.Theme.getSize("section_icon").width : 0 - Layout.preferredHeight: visible ? UM.Theme.getSize("section_icon").height : 0 - Layout.alignment: Qt.AlignTop + // TODO: on hover + } - // TODO: on hover + Rectangle + { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + + visible: false + // TODO: on hover + } } Label -- cgit v1.2.3 From d186912596d2882ecbe5da584e35f970eef77849 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:45:44 +0100 Subject: Correcter font sizes according to design Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index ab26699ce8..2472e71348 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -92,7 +92,7 @@ Rectangle Layout.alignment: Qt.AlignTop text: packageData.packageVersion - font: UM.Theme.getFont("small") + font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") } @@ -121,7 +121,7 @@ Rectangle property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description - font: UM.Theme.getFont("default") + font: UM.Theme.getFont("medium") color: UM.Theme.getColor("text") maximumLineCount: 2 wrapMode: Text.Wrap @@ -131,7 +131,7 @@ Rectangle { if(truncated && line.isLast) { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… "); + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - UM.Theme.getSize("default_margin").width; if(line.implicitWidth > max_line_width) { line.width = max_line_width; @@ -232,6 +232,6 @@ Rectangle FontMetrics { id: fontMetrics - font: UM.Theme.getFont("default") + font: UM.Theme.getFont("medium") } } -- cgit v1.2.3 From 1efdd9205bf36f3aac5dca1c1ea0b7955cad11af Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 9 Nov 2021 17:47:32 +0100 Subject: Use primary colour for verified icon Contributes to issue CURA-8561. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 2472e71348..35eb94c422 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -69,7 +69,7 @@ Rectangle width: UM.Theme.getSize("section_icon").width height: UM.Theme.getSize("section_icon").height - color: UM.Theme.getColor("icon") + color: UM.Theme.getColor("primary") visible: packageData.isVerified source: UM.Theme.getIcon("CheckCircle") -- cgit v1.2.3 From 51de2340825c46a798aeffe72a0d8d69012b6882 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Nov 2021 18:09:36 +0100 Subject: Links, hovers, ensmallify layout. part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 142 +++++++++++++++++----- resources/themes/cura-light/theme.json | 1 + 2 files changed, 110 insertions(+), 33 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 35eb94c422..05a74de228 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -13,19 +13,18 @@ Rectangle property var packageData width: parent ? parent.width : 0 - height: childrenRect.height + UM.Theme.getSize("default_margin").height * 2 + height: childrenRect.height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width RowLayout { - width: parent.width - UM.Theme.getSize("default_margin").width * 2 - height: childrenRect.height - x: UM.Theme.getSize("default_margin").width - y: UM.Theme.getSize("default_margin").height + width: parent.width - UM.Theme.getSize("thin_margin").width * 2 + x: UM.Theme.getSize("thin_margin").width + y: UM.Theme.getSize("thin_margin").height - spacing: UM.Theme.getSize("default_margin").width + spacing: UM.Theme.getSize("thin_margin").width Image //Separate column for icon on the left. { @@ -42,14 +41,11 @@ Rectangle Layout.preferredHeight: childrenRect.height Layout.alignment: Qt.AlignTop - spacing: UM.Theme.getSize("default_margin").height - RowLayout //Title row. { + Layout.alignment: Qt.AlignTop width: parent.width - spacing: UM.Theme.getSize("default_margin").width - Label { Layout.alignment: Qt.AlignTop @@ -64,25 +60,59 @@ Rectangle spacing: parent.spacing Layout.alignment: Qt.AlignTop - UM.RecolorImage + Control { - width: UM.Theme.getSize("section_icon").width - height: UM.Theme.getSize("section_icon").height + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignTop + + enabled: packageData.isVerified + + Cura.ToolTip + { + tooltipText: catalog.i18nc("@info", "Verified") + visible: parent.hovered + } + + UM.RecolorImage + { + anchors.fill: parent - color: UM.Theme.getColor("primary") - visible: packageData.isVerified - source: UM.Theme.getIcon("CheckCircle") + color: UM.Theme.getColor("primary") + visible: packageData.isVerified + source: UM.Theme.getIcon("CheckCircle") + } - // TODO: on hover + //NOTE: Can we link to something here? (Probably a static link explaining what verified is): + // onClicked: Qt.openUrlExternally( XXXXXX ) } - Rectangle - { // placeholder for 'certified material' icon+link whenever we implement the materials part of this card - width: UM.Theme.getSize("section_icon").width - height: UM.Theme.getSize("section_icon").height + Control + { + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignTop + + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation - visible: false - // TODO: on hover + Cura.ToolTip + { + tooltipText: "" // TODO + visible: parent.hovered + } + + UM.RecolorImage + { + anchors.fill: parent + + color: UM.Theme.getColor("primary") + visible: packageData.isVerified + source: UM.Theme.getIcon("CheckCircle") // TODO + } + + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO } } @@ -96,16 +126,52 @@ Rectangle color: UM.Theme.getColor("text") } - UM.RecolorImage + Button { - Layout.preferredWidth: UM.Theme.getSize("section_icon").width - Layout.preferredHeight: UM.Theme.getSize("section_icon").height + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height Layout.alignment: Qt.AlignTop + UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("icon") + source: UM.Theme.getIcon("LinkExternal") + } + + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } + } + + RowLayout + { + width: parent.width - UM.Theme.getSize("thin_margin").width * 2 + height: childrenRect.height + x: UM.Theme.getSize("thin_margin").width + y: UM.Theme.getSize("thin_margin").height + + spacing: UM.Theme.getSize("thin_margin").width + + enabled: false // remove + visible: false // replace w state? + // TODO: hide/unhide on states (folded versus header state) + + UM.RecolorImage + { + id: downloadCountIcon + width: UM.Theme.getSize("card_tiny_icon").height + height: UM.Theme.getSize("card_tiny_icon").height color: UM.Theme.getColor("icon") - source: UM.Theme.getIcon("LinkExternal") - // TODO: on clicked url + source: UM.Theme.getIcon("Download") + } + + Label + { + id: downloadCountLabel + anchors.left: downloadCountIcon.right + + text: packageData.downloadCount } } @@ -161,6 +227,9 @@ Rectangle rightPadding: UM.Theme.getSize("wide_margin").width textFont: descriptionLabel.font isIconOnRightSide: true + + // NOTE: Is this the right URL for this action? + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } Label @@ -179,13 +248,13 @@ Rectangle RowLayout //Author and action buttons. { width: parent.width - - spacing: UM.Theme.getSize("default_margin").width + Layout.alignment: Qt.AlignBottom + spacing: UM.Theme.getSize("thin_margin").width Label { id: authorBy - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignBottom text: catalog.i18nc("@label", "By") font: UM.Theme.getFont("default") @@ -196,32 +265,39 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignBottom text: packageData.authorName textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color leftPadding: 0 rightPadding: 0 iconSource: UM.Theme.getIcon("LinkExternal") isIconOnRightSide: true - // TODO on clicked (is link) -> MouseArea? + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } Cura.SecondaryButton { + Layout.alignment: Qt.AlignBottom + Layout.preferredHeight: authorBy.height text: catalog.i18nc("@button", "Disable") // not functional right now } Cura.SecondaryButton { + Layout.alignment: Qt.AlignBottom + Layout.preferredHeight: authorBy.height text: catalog.i18nc("@button", "Uninstall") // not functional right now } Cura.PrimaryButton { + Layout.alignment: Qt.AlignBottom + Layout.preferredHeight: authorBy.height text: catalog.i18nc("@button", "Update") // not functional right now } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 07ed1a899d..a4f3172036 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -556,6 +556,7 @@ "card": [25.0, 8.75], "card_icon": [6.0, 6.0], + "card_tiny_icon": [1.2, 1.2], "button": [4, 4], "button_icon": [2.5, 2.5], -- cgit v1.2.3 From c1f2da8820794dea4f1c38b1124d9c77c0086bd4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Nov 2021 17:30:43 +0100 Subject: Layout fixes. Prevent 'height' based binding loops. part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 23 +++++++++-------------- resources/qml/ToolTip.qml | 4 ++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 05a74de228..00f66751e3 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,7 @@ Rectangle { property var packageData - width: parent ? parent.width : 0 + width: parent ? parent.width - UM.Theme.getSize("default_margin").width : 0 height: childrenRect.height color: UM.Theme.getColor("main_background") @@ -21,10 +21,11 @@ Rectangle RowLayout { width: parent.width - UM.Theme.getSize("thin_margin").width * 2 + height: childrenRect.height + UM.Theme.getSize("thin_margin").height * 2 x: UM.Theme.getSize("thin_margin").width y: UM.Theme.getSize("thin_margin").height - spacing: UM.Theme.getSize("thin_margin").width + spacing: UM.Theme.getSize("default_margin").width Image //Separate column for icon on the left. { @@ -38,7 +39,6 @@ Rectangle Column { Layout.fillWidth: true - Layout.preferredHeight: childrenRect.height Layout.alignment: Qt.AlignTop RowLayout //Title row. @@ -169,8 +169,6 @@ Rectangle Label { id: downloadCountLabel - anchors.left: downloadCountIcon.right - text: packageData.downloadCount } } @@ -248,13 +246,13 @@ Rectangle RowLayout //Author and action buttons. { width: parent.width - Layout.alignment: Qt.AlignBottom + Layout.alignment: Qt.AlignTop spacing: UM.Theme.getSize("thin_margin").width Label { id: authorBy - Layout.alignment: Qt.AlignBottom + Layout.alignment: Qt.AlignTop text: catalog.i18nc("@label", "By") font: UM.Theme.getFont("default") @@ -265,7 +263,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignBottom + Layout.alignment: Qt.AlignTop text: packageData.authorName textFont: UM.Theme.getFont("default_bold") @@ -280,24 +278,21 @@ Rectangle Cura.SecondaryButton { - Layout.alignment: Qt.AlignBottom - Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Disable") // not functional right now } Cura.SecondaryButton { - Layout.alignment: Qt.AlignBottom - Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Uninstall") // not functional right now } Cura.PrimaryButton { - Layout.alignment: Qt.AlignBottom - Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Update") // not functional right now } diff --git a/resources/qml/ToolTip.qml b/resources/qml/ToolTip.qml index 3157f81d89..c4edc5a361 100644 --- a/resources/qml/ToolTip.qml +++ b/resources/qml/ToolTip.qml @@ -38,7 +38,7 @@ ToolTip onAboutToHide: hide() // If the text is not set, just set the height to 0 to prevent it from showing - height: text != "" ? label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width: 0 + height: label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width x: { @@ -74,7 +74,7 @@ ToolTip } function show() { - opacity = 1 + opacity = text != "" ? 1 : 0 } function hide() { -- cgit v1.2.3 From 82f140aa395c3d54221ae2f11df741b4627cbe30 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Nov 2021 18:07:21 +0100 Subject: Folded versus unfolded. Hide disable/uninstall/install buttons, they're not active anyway and it's not part of this ticket in what state they should be hidden or not. What is part of the folded versus header is the download count row. (Also adapt link color.) part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 59 ++++++++++++++++++----- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 00f66751e3..35bf17b0c9 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -18,6 +18,30 @@ Rectangle color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width + states: + [ + State + { + name: "Folded" + when: true // TODO + PropertyChanges + { + target: downloadCountRow + visible: false + } + }, + State + { + name: "Header" + when: false // TODO + PropertyChanges + { + target: downloadCountRow + visible: true + } + } + ] + RowLayout { width: parent.width - UM.Theme.getSize("thin_margin").width * 2 @@ -128,15 +152,23 @@ Rectangle Button { + id: externalLinkButton + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height Layout.alignment: Qt.AlignTop - UM.RecolorImage + Rectangle { anchors.fill: parent - color: UM.Theme.getColor("icon") - source: UM.Theme.getIcon("LinkExternal") + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") + + UM.RecolorImage + { + anchors.fill: parent + color: externalLinkButton.hovered ? UM.Theme.getColor("text_link") : UM.Theme.getColor("text") + source: UM.Theme.getIcon("LinkExternal") + } } onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) @@ -145,21 +177,21 @@ Rectangle RowLayout { - width: parent.width - UM.Theme.getSize("thin_margin").width * 2 + id: downloadCountRow + + width: childrenRect.width height: childrenRect.height x: UM.Theme.getSize("thin_margin").width y: UM.Theme.getSize("thin_margin").height spacing: UM.Theme.getSize("thin_margin").width - enabled: false // remove - visible: false // replace w state? - // TODO: hide/unhide on states (folded versus header state) + visible: false // start up invisible (see states) UM.RecolorImage { id: downloadCountIcon - width: UM.Theme.getSize("card_tiny_icon").height + width: UM.Theme.getSize("card_tiny_icon").width height: UM.Theme.getSize("card_tiny_icon").height color: UM.Theme.getColor("icon") @@ -278,23 +310,26 @@ Rectangle Cura.SecondaryButton { + id: disableButton Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Disable") - // not functional right now + visible: false // not functional right now, also only when unfolding and required } Cura.SecondaryButton { + id: uninstallButton Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Uninstall") - // not functional right now + visible: false // not functional right now, also only when unfolding and required } Cura.PrimaryButton { + id: installButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") - // not functional right now + text: catalog.i18nc("@button", "Update") // OR Download, if new! + visible: false // not functional right now, also only when unfolding and required } } } -- cgit v1.2.3 From 4a7a74cba61d295750a86e5e99e93adcf3145523 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 08:45:09 +0100 Subject: Also make description area foldable. part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 35bf17b0c9..bbe7cd4de6 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -29,6 +29,11 @@ Rectangle target: downloadCountRow visible: false } + PropertyChanges + { + target: descriptionArea + visible: true + } }, State { @@ -39,6 +44,11 @@ Rectangle target: downloadCountRow visible: true } + PropertyChanges + { + target: descriptionArea + visible: false + } } ] @@ -186,8 +196,6 @@ Rectangle spacing: UM.Theme.getSize("thin_margin").width - visible: false // start up invisible (see states) - UM.RecolorImage { id: downloadCountIcon @@ -207,6 +215,7 @@ Rectangle Item { + id: descriptionArea width: parent.width height: descriptionLabel.height -- cgit v1.2.3 From d47b2fb5dd6a1a8559ce1ce7006f0218a62a0500 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 08:52:48 +0100 Subject: Control should be (in)visible, not (just) image. part oc CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index bbe7cd4de6..3a185a3e02 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -101,6 +101,7 @@ Rectangle Layout.alignment: Qt.AlignTop enabled: packageData.isVerified + visible: packageData.isVerified Cura.ToolTip { @@ -113,7 +114,6 @@ Rectangle anchors.fill: parent color: UM.Theme.getColor("primary") - visible: packageData.isVerified source: UM.Theme.getIcon("CheckCircle") } @@ -142,7 +142,6 @@ Rectangle anchors.fill: parent color: UM.Theme.getColor("primary") - visible: packageData.isVerified source: UM.Theme.getIcon("CheckCircle") // TODO } -- cgit v1.2.3 From 977a12c989895072e8ca1ceed812000c8c2d1962 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 09:52:24 +0100 Subject: Steal search-bar from DigitalFactory. Make 'SearchBar' into a reusable component, so it can be used in the (new) Marketplace search. Also now at least the word 'Search' can be translated ;-) part of CURA-8559 --- .../resources/qml/SelectProjectPage.qml | 26 ++-------------- resources/qml/SearchBar.qml | 36 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 resources/qml/SearchBar.qml diff --git a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml index 50d3cb61c5..1114900c04 100644 --- a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml @@ -9,7 +9,7 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.1 import UM 1.2 as UM -import Cura 1.6 as Cura +import Cura 1.7 as Cura import DigitalFactory 1.0 as DF @@ -44,32 +44,10 @@ Item height: childrenRect.height spacing: UM.Theme.getSize("default_margin").width - Cura.TextField + Cura.SearchBar { id: searchBar - Layout.fillWidth: true - implicitHeight: createNewProjectButton.height - leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2 - onTextEdited: manager.projectFilter = text //Update the search filter when editing this text field. - - placeholderText: "Search" - - UM.RecolorImage - { - id: searchIcon - - anchors - { - verticalCenter: parent.verticalCenter - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - } - source: UM.Theme.getIcon("search") - height: UM.Theme.getSize("small_button_icon").height - width: height - color: UM.Theme.getColor("text") - } } Cura.SecondaryButton diff --git a/resources/qml/SearchBar.qml b/resources/qml/SearchBar.qml new file mode 100644 index 0000000000..de723d1c84 --- /dev/null +++ b/resources/qml/SearchBar.qml @@ -0,0 +1,36 @@ +// 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.7 as Cura + +Cura.TextField +{ + UM.I18nCatalog { id: catalog; name: "cura" } + + Layout.fillWidth: true + implicitHeight: createNewProjectButton.height + leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2 + + placeholderText: catalog.i18nc("@placeholder", "Search") + + UM.RecolorImage + { + id: searchIcon + + anchors + { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + } + source: UM.Theme.getIcon("search") + height: UM.Theme.getSize("small_button_icon").height + width: height + color: UM.Theme.getColor("text") + } +} -- cgit v1.2.3 From b5d58f78d7a5ab32cbf9a399b9faf3c303b2d3a1 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 11:49:37 +0100 Subject: Fix: Actually make SearchBar-component independant. It still depended on the Digital Library 'parent'. part of CURA-8559 --- plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml | 2 ++ resources/qml/SearchBar.qml | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml index 1114900c04..9ebf264e0f 100644 --- a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml @@ -47,6 +47,8 @@ Item Cura.SearchBar { id: searchBar + Layout.fillWidth: true + implicitHeight: createNewProjectButton.height onTextEdited: manager.projectFilter = text //Update the search filter when editing this text field. } diff --git a/resources/qml/SearchBar.qml b/resources/qml/SearchBar.qml index de723d1c84..ce1808252e 100644 --- a/resources/qml/SearchBar.qml +++ b/resources/qml/SearchBar.qml @@ -12,8 +12,6 @@ Cura.TextField { UM.I18nCatalog { id: catalog; name: "cura" } - Layout.fillWidth: true - implicitHeight: createNewProjectButton.height leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2 placeholderText: catalog.i18nc("@placeholder", "Search") @@ -28,7 +26,7 @@ Cura.TextField left: parent.left leftMargin: UM.Theme.getSize("default_margin").width } - source: UM.Theme.getIcon("search") + source: UM.Theme.getIcon("Magnifier") height: UM.Theme.getSize("small_button_icon").height width: height color: UM.Theme.getColor("text") -- cgit v1.2.3 From 7432c0d8f01e6783036a7de532de096b0e28bf3d Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 12:11:45 +0100 Subject: Add (not yet operational) search-bar to new Marketplace. part of CURA-8559 --- plugins/Marketplace/resources/qml/Marketplace.qml | 87 ++++++++++++++++------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 430c237252..1422a0eece 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -70,46 +70,79 @@ Window } } + // Search & Top-Level Tabs Item { - Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height - - ManagePackagesButton + Layout.preferredWidth: parent.width - 2 * UM.Theme.getSize("thin_margin").width + RowLayout { - id: managePackagesButton - - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_margin").width + width: parent.width + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("thin_margin").width - onClicked: + Rectangle { - content.source = "ManagedPackages.qml" + Layout.preferredHeight: parent.height + Layout.preferredWidth: UM.Theme.getSize("thin_margin").width } - } - // Page selection. - TabBar - { - id: pageSelectionTabBar - anchors.right: managePackagesButton.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - height: UM.Theme.getSize("button_icon").height - spacing: 0 + Cura.SearchBar + { + id: searchBar + Layout.preferredHeight: parent.height + Layout.fillWidth: true + //onTextEdited: // TODO! + } - PackageTypeTab + // Page selection. + TabBar { + id: pageSelectionTabBar + height: parent.height width: implicitWidth - padding: UM.Theme.getSize("default_margin").width/2 - text: catalog.i18nc("@button", "Plugins") - onClicked: content.source = "Plugins.qml" + spacing: 0 + + PackageTypeTab + { + id: pluginTabText + width: implicitWidth + padding: UM.Theme.getSize("thin_margin").width + text: catalog.i18nc("@button", "Plugins") + onClicked: content.source = "Plugins.qml" + } + PackageTypeTab + { + id: materialsTabText + width: implicitWidth + padding: UM.Theme.getSize("thin_margin").width + text: catalog.i18nc("@button", "Materials") + onClicked: content.source = "Materials.qml" + } } - PackageTypeTab + TextMetrics { - width: implicitWidth - padding: Math.round(UM.Theme.getSize("default_margin").width / 2) - text: catalog.i18nc("@button", "Materials") - onClicked: content.source = "Materials.qml" + id: pluginTabTextMetrics + text: pluginTabText.text + font: pluginTabText.font + } + TextMetrics + { + id: materialsTabTextMetrics + text: materialsTabText.text + font: materialsTabText.font + } + + ManagePackagesButton + { + id: managePackagesButton + height: parent.height + width: UM.Theme.getSize("button_icon").width + + onClicked: + { + content.source = "ManagedPackages.qml" + } } } } -- cgit v1.2.3 From d7ac307ace75917882386c9cf4244013a4e5a319 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 14:01:05 +0100 Subject: Type in the search-bar and the remote package list reacts. It doesn't do any actual searching yet though. Also switching between page doesn't work like it's supposed to yet (and probalby more of that sort of cases). part of CURA-8559 --- plugins/Marketplace/RemotePackageList.py | 24 +++++++++++++++++++++-- plugins/Marketplace/resources/qml/Marketplace.qml | 9 ++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 8fa75453c1..150d9901f1 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -32,6 +32,7 @@ class RemotePackageList(PackageList): self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._package_type_filter = "" + self._search_string = "" self._request_url = self._initialRequestUrl() self.isLoadingChanged.emit() @@ -69,6 +70,7 @@ class RemotePackageList(PackageList): self._request_url = self._initialRequestUrl() packageTypeFilterChanged = pyqtSignal() + searchStringChanged = pyqtSignal() def setPackageTypeFilter(self, new_filter: str) -> None: if new_filter != self._package_type_filter: @@ -76,6 +78,12 @@ class RemotePackageList(PackageList): self.reset() self.packageTypeFilterChanged.emit() + def setSearchString(self, new_search: str) -> None: + if new_search != self._search_string: + self._search_string = new_search + self.reset() + self.searchStringChanged.emit() + @pyqtProperty(str, fset = setPackageTypeFilter, notify = packageTypeFilterChanged) def packageTypeFilter(self) -> str: """ @@ -84,14 +92,26 @@ class RemotePackageList(PackageList): """ return self._package_type_filter + @pyqtProperty(str, fset = setSearchString, notify = searchStringChanged) + def searchString(self) -> str: + """ + Get the string the user is currently searching for within the packages, or an empty string if no extra search + filter has to be applied. Does not override package-type filter! + :return: String the user is searching for. Empty denotes 'no search filter'. + """ + return self._search_string + def _initialRequestUrl(self) -> str: """ Get the URL to request the first paginated page with. :return: A URL to request. """ + request_url = f"{Marketplace.PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" 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}" + request_url += f"&package_type={self._package_type_filter}" + if self._search_string != "": + request_url += f"" # TODO + return request_url def _parseResponse(self, reply: "QNetworkReply") -> None: """ diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 1422a0eece..e92ac44c1d 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -14,6 +14,8 @@ Window id: marketplaceDialog property variant catalog: UM.I18nCatalog { name: "cura" } + signal searchStringChanged(string new_search) + minimumWidth: UM.Theme.getSize("modal_window_minimum").width minimumHeight: UM.Theme.getSize("modal_window_minimum").height width: minimumWidth @@ -92,7 +94,7 @@ Window id: searchBar Layout.preferredHeight: parent.height Layout.fillWidth: true - //onTextEdited: // TODO! + onTextEdited: marketplaceDialog.searchStringChanged(text) } // Page selection. @@ -168,6 +170,11 @@ Window function onLoaded() { pageTitle.text = content.item.pageTitle + searchStringChanged.connect(onSearchStringChanged) + } + function onSearchStringChanged(new_search) + { + content.item.model.searchString = new_search } } } -- cgit v1.2.3 From 79f7724923198e8ac17bd409507a6d2ad9de013c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 14:30:09 +0100 Subject: Actually set search string + fix code style warnings. It now works! Sort of. Turns out you have to manually click 'Load More' each time now :-) This is also at least partially explains the 'cases' mentioned in previous commit (when switching tabs). part of CURA-8559 --- plugins/Marketplace/RemotePackageList.py | 2 +- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 2 +- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 150d9901f1..156e7bbf0f 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -110,7 +110,7 @@ class RemotePackageList(PackageList): if self._package_type_filter != "": request_url += f"&package_type={self._package_type_filter}" if self._search_string != "": - request_url += f"" # TODO + request_url += f"&search={self._search_string}" return request_url def _parseResponse(self, reply: "QNetworkReply") -> None: diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 31b97d89ed..a2ebfe0df7 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -18,7 +18,7 @@ Button background: Rectangle { color: backgroundColor - border.color: transparent + border.color: "transparent" radius: Math.round(width * 0.5) } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index e92ac44c1d..7cfb68883f 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -170,9 +170,9 @@ Window function onLoaded() { pageTitle.text = content.item.pageTitle - searchStringChanged.connect(onSearchStringChanged) + searchStringChanged.connect(handleSearchStringChanged) } - function onSearchStringChanged(new_search) + function handleSearchStringChanged(new_search) { content.item.model.searchString = new_search } -- cgit v1.2.3 From 44242dcd02d37e01a2c1ebefab48b5c8bd0c7992 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 15:20:46 +0100 Subject: Auto-load next batch with searched-for text in Marketplace. part of CURA-8559 --- plugins/Marketplace/RemotePackageList.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 156e7bbf0f..e7df498fbf 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -32,8 +32,10 @@ class RemotePackageList(PackageList): self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._package_type_filter = "" - self._search_string = "" + self._requested_search_string = "" + self._current_search_string = "" self._request_url = self._initialRequestUrl() + self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() def __del__(self) -> None: @@ -79,10 +81,8 @@ class RemotePackageList(PackageList): self.packageTypeFilterChanged.emit() def setSearchString(self, new_search: str) -> None: - if new_search != self._search_string: - self._search_string = new_search - self.reset() - self.searchStringChanged.emit() + self._requested_search_string = new_search + self._onLoadingChanged() @pyqtProperty(str, fset = setPackageTypeFilter, notify = packageTypeFilterChanged) def packageTypeFilter(self) -> str: @@ -95,11 +95,18 @@ class RemotePackageList(PackageList): @pyqtProperty(str, fset = setSearchString, notify = searchStringChanged) def searchString(self) -> str: """ - Get the string the user is currently searching for within the packages, or an empty string if no extra search - filter has to be applied. Does not override package-type filter! + Get the string the user is currently searching for (as in: the list is updating) within the packages, + or an empty string if no extra search filter has to be applied. Does not override package-type filter! :return: String the user is searching for. Empty denotes 'no search filter'. """ - return self._search_string + return self._current_search_string + + def _onLoadingChanged(self) -> None: + if self._requested_search_string != self._current_search_string and not self._is_loading: + self._current_search_string = self._requested_search_string + self.reset() + self.updatePackages() + self.searchStringChanged.emit() def _initialRequestUrl(self) -> str: """ @@ -109,8 +116,8 @@ class RemotePackageList(PackageList): request_url = f"{Marketplace.PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" if self._package_type_filter != "": request_url += f"&package_type={self._package_type_filter}" - if self._search_string != "": - request_url += f"&search={self._search_string}" + if self._current_search_string != "": + request_url += f"&search={self._current_search_string}" return request_url def _parseResponse(self, reply: "QNetworkReply") -> None: -- cgit v1.2.3 From 24eaad4c6d87889b3d47ae21dfdcd6d714d11029 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Nov 2021 15:37:59 +0100 Subject: Make switching Marketplace-tabs work with search-bar. Reset search-bar (text) when switching tabs. Also hide search-bar (at least for now) when dealing with the managed packages tab. part of CURA-8559 --- plugins/Marketplace/resources/qml/Marketplace.qml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 7cfb68883f..ea60bf0c7e 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -86,7 +86,8 @@ Window Rectangle { Layout.preferredHeight: parent.height - Layout.preferredWidth: UM.Theme.getSize("thin_margin").width + Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 + Layout.fillWidth: ! searchBar.visible } Cura.SearchBar @@ -94,7 +95,7 @@ Window id: searchBar Layout.preferredHeight: parent.height Layout.fillWidth: true - onTextEdited: marketplaceDialog.searchStringChanged(text) + onTextEdited: searchStringChanged(text) } // Page selection. @@ -111,7 +112,12 @@ Window width: implicitWidth padding: UM.Theme.getSize("thin_margin").width text: catalog.i18nc("@button", "Plugins") - onClicked: content.source = "Plugins.qml" + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Plugins.qml" + } } PackageTypeTab { @@ -119,7 +125,12 @@ Window width: implicitWidth padding: UM.Theme.getSize("thin_margin").width text: catalog.i18nc("@button", "Materials") - onClicked: content.source = "Materials.qml" + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Materials.qml" + } } } TextMetrics @@ -143,6 +154,8 @@ Window onClicked: { + searchBar.text = "" + searchBar.visible = false content.source = "ManagedPackages.qml" } } -- cgit v1.2.3 From 87f94e680bf0da3f96d03e2a72c6848cf7451c3c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 16 Nov 2021 10:20:08 +0100 Subject: Use re-usable search-bar for Settings search as well. done as part of CURA-8559 --- resources/qml/SearchBar.qml | 1 + resources/qml/Settings/SettingView.qml | 33 +++++---------------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/resources/qml/SearchBar.qml b/resources/qml/SearchBar.qml index ce1808252e..4d9c003653 100644 --- a/resources/qml/SearchBar.qml +++ b/resources/qml/SearchBar.qml @@ -15,6 +15,7 @@ Cura.TextField leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2 placeholderText: catalog.i18nc("@placeholder", "Search") + font.italic: true UM.RecolorImage { diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 074946c6bd..5fc0b60381 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -41,39 +41,19 @@ Item repeat: false } - Cura.TextField + Cura.SearchBar { id: filter height: parent.height anchors.left: parent.left anchors.right: parent.right - leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2 - placeholderText: catalog.i18nc("@label:textbox", "Search settings") - font.italic: true + + placeholderText: catalog.i18nc("@label:textbox", "Search settings") // Overwrite property var expandedCategories property bool lastFindingSettings: false - UM.RecolorImage - { - id: searchIcon - - anchors - { - verticalCenter: parent.verticalCenter - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - } - source: UM.Theme.getIcon("search") - height: UM.Theme.getSize("small_button_icon").height - width: height - color: UM.Theme.getColor("text") - } - - onTextChanged: - { - settingsSearchTimer.restart() - } + onTextChanged: settingsSearchTimer.restart() onEditingFinished: { @@ -86,10 +66,7 @@ Item } } - Keys.onEscapePressed: - { - filter.text = "" - } + Keys.onEscapePressed: filter.text = "" function updateDefinitionModel() { -- cgit v1.2.3 From 6df6dab6f01db427a9b73f1012a2d5f28fd9c3fb Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 16 Nov 2021 10:38:00 +0100 Subject: Remove explicit transparent border on button. done as part of CURA-8559 --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index a2ebfe0df7..bf122140a7 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -18,9 +18,7 @@ Button background: Rectangle { color: backgroundColor - border.color: "transparent" radius: Math.round(width * 0.5) - } Cura.ToolTip -- cgit v1.2.3 From 74193ffff9ca6bb496c1ffe6de298d3544ac3a6c Mon Sep 17 00:00:00 2001 From: 10r3n20 Date: Tue, 16 Nov 2021 11:29:48 +0100 Subject: moved padding to PackageTybeTab and replaced default margin with narrow margin --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 1 - plugins/Marketplace/resources/qml/Marketplace.qml | 2 -- plugins/Marketplace/resources/qml/PackageTypeTab.qml | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 31b97d89ed..8d64b27609 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -20,7 +20,6 @@ Button color: backgroundColor border.color: transparent radius: Math.round(width * 0.5) - } Cura.ToolTip diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 430c237252..93d616209b 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -100,14 +100,12 @@ Window PackageTypeTab { width: implicitWidth - padding: UM.Theme.getSize("default_margin").width/2 text: catalog.i18nc("@button", "Plugins") onClicked: content.source = "Plugins.qml" } PackageTypeTab { width: implicitWidth - padding: Math.round(UM.Theme.getSize("default_margin").width / 2) text: catalog.i18nc("@button", "Materials") onClicked: content.source = "Materials.qml" } diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml index 31fbabc294..6f54932c07 100644 --- a/plugins/Marketplace/resources/qml/PackageTypeTab.qml +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -8,6 +8,7 @@ import UM 1.0 as UM TabButton { property string pageTitle + padding: UM.Theme.getSize("narrow_margin").width background: Rectangle { -- cgit v1.2.3 From e4cd310303a304b78ae0db64693799f6ea4c68bd Mon Sep 17 00:00:00 2001 From: 10r3n20 Date: Tue, 16 Nov 2021 11:37:52 +0100 Subject: added hover state to inactive tabs --- plugins/Marketplace/resources/qml/PackageTypeTab.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml index 6f54932c07..4d82333b02 100644 --- a/plugins/Marketplace/resources/qml/PackageTypeTab.qml +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -9,11 +9,15 @@ TabButton { property string pageTitle padding: UM.Theme.getSize("narrow_margin").width + hoverEnabled: true + property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") + property color activeBackgroundColor : UM.Theme.getColor("main_background") + background: Rectangle { anchors.fill: parent - color: parent.checked ? UM.Theme.getColor("main_background") : UM.Theme.getColor("detail_background") + color: parent.checked ? activeBackgroundColor : inactiveBackgroundColor border.color: UM.Theme.getColor("detail_background") border.width: UM.Theme.getSize("thick_lining").width } -- cgit v1.2.3 From 0e646a97f2852bb94e8ec11ad7c7de7b62ac5ada Mon Sep 17 00:00:00 2001 From: 10r3n20 Date: Tue, 16 Nov 2021 11:40:24 +0100 Subject: made font bold in tabs to be more aligned in weight with the gear icon --- plugins/Marketplace/resources/qml/PackageTypeTab.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml index 4d82333b02..0520b74fb6 100644 --- a/plugins/Marketplace/resources/qml/PackageTypeTab.qml +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -13,7 +13,6 @@ TabButton property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") property color activeBackgroundColor : UM.Theme.getColor("main_background") - background: Rectangle { anchors.fill: parent @@ -25,7 +24,7 @@ TabButton contentItem: Label { text: parent.text - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") width: contentWidth anchors.centerIn: parent -- cgit v1.2.3 From 4c6441f65e6c294173e2d87db2fd8b1323f8225b Mon Sep 17 00:00:00 2001 From: 10r3n20 Date: Tue, 16 Nov 2021 14:02:07 +0100 Subject: moved manage packages button inside the tabBar and adjusted styling --- .../resources/qml/ManagePackagesButton.qml | 18 +++++++++++------- plugins/Marketplace/resources/qml/Marketplace.qml | 19 +++++-------------- plugins/Marketplace/resources/qml/PackageTypeTab.qml | 1 + 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 8d64b27609..0dc33d62f3 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -7,18 +7,21 @@ import Cura 1.6 as Cura import QtQuick 2.15 import QtQuick.Controls 2.15 -Button +TabButton { id: root - width: UM.Theme.getSize("button_icon").width + width: UM.Theme.getSize("button_icon").width+UM.Theme.getSize("narrow_margin").width height: UM.Theme.getSize("button_icon").height hoverEnabled: true - property color backgroundColor: hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button") + property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") + property color activeBackgroundColor : UM.Theme.getColor("main_background") + leftInset: UM.Theme.getSize("narrow_margin").width background: Rectangle { - color: backgroundColor - border.color: transparent + color: parent.checked ? activeBackgroundColor : inactiveBackgroundColor + border.color: parent.checked ? UM.Theme.getColor("detail_background") : "transparent" + border.width: UM.Theme.getSize("thick_lining").width radius: Math.round(width * 0.5) } @@ -39,7 +42,8 @@ Button color: UM.Theme.getColor("icon") source: UM.Theme.getIcon("Settings") - anchors.centerIn: parent - + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: Math.round(UM.Theme.getSize("narrow_margin").width /2) + anchors.verticalCenter: parent.verticalCenter } } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 93d616209b..e18adf51f2 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -75,24 +75,11 @@ Window Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height - ManagePackagesButton - { - id: managePackagesButton - - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_margin").width - - onClicked: - { - content.source = "ManagedPackages.qml" - } - } - // Page selection. TabBar { id: pageSelectionTabBar - anchors.right: managePackagesButton.left + anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width height: UM.Theme.getSize("button_icon").height spacing: 0 @@ -109,6 +96,10 @@ Window text: catalog.i18nc("@button", "Materials") onClicked: content.source = "Materials.qml" } + ManagePackagesButton + { + onClicked: content.source = "ManagedPackages.qml" + } } } diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml index 0520b74fb6..79eaa9a16c 100644 --- a/plugins/Marketplace/resources/qml/PackageTypeTab.qml +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -9,6 +9,7 @@ TabButton { property string pageTitle padding: UM.Theme.getSize("narrow_margin").width + horizontalPadding: UM.Theme.getSize("default_margin").width hoverEnabled: true property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") property color activeBackgroundColor : UM.Theme.getColor("main_background") -- cgit v1.2.3 From 7c29e69f557cd7b59b8dd9d86a845ebfa97dc05d Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 16 Nov 2021 16:07:54 +0100 Subject: Small UX adjustments. part of CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 3a185a3e02..b93720f300 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,7 @@ Rectangle { property var packageData - width: parent ? parent.width - UM.Theme.getSize("default_margin").width : 0 + width: parent ? parent.width - UM.Theme.getSize("thin_margin").width : 0 height: childrenRect.height color: UM.Theme.getColor("main_background") @@ -109,12 +109,17 @@ Rectangle visible: parent.hovered } - UM.RecolorImage + Rectangle { anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") + color: UM.Theme.getColor("action_button_hovered") + radius: width + UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") + } } //NOTE: Can we link to something here? (Probably a static link explaining what verified is): @@ -170,12 +175,13 @@ Rectangle Rectangle { anchors.fill: parent - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") + radius: width + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" UM.RecolorImage { anchors.fill: parent - color: externalLinkButton.hovered ? UM.Theme.getColor("text_link") : UM.Theme.getColor("text") + color: UM.Theme.getColor("text") source: UM.Theme.getIcon("LinkExternal") } } -- cgit v1.2.3 From e44a58b3a37b283e97ea4ee614d00f7d77ebf5ed Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 16 Nov 2021 16:51:09 +0100 Subject: Use reusable simple button instead of redefining it --- plugins/Marketplace/resources/qml/PackageCard.qml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index b93720f300..85df7ae0f1 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -164,7 +164,7 @@ Rectangle color: UM.Theme.getColor("text") } - Button + UM.SimpleButton { id: externalLinkButton @@ -172,20 +172,10 @@ Rectangle Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height Layout.alignment: Qt.AlignTop - Rectangle - { - anchors.fill: parent - radius: width - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" - - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("text") - source: UM.Theme.getIcon("LinkExternal") - } - } - + iconSource: UM.Theme.getIcon("LinkExternal") + hoverColor: UM.Theme.getColor("text_link") + backgroundColor: UM.Theme.getColor("detail_background") + hoverBackgroundColor: UM.Theme.getColor("action_button_hovered") onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } } -- cgit v1.2.3 From 980cc22529ac7d9099b4228075c6c57df9c87626 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 16 Nov 2021 18:02:56 +0100 Subject: Simplify the layout of the packageCard CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 448 +++++++++++----------- 1 file changed, 220 insertions(+), 228 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 85df7ae0f1..770731375f 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -13,7 +13,7 @@ Rectangle property var packageData width: parent ? parent.width - UM.Theme.getSize("thin_margin").width : 0 - height: childrenRect.height + height: childrenRect.height + UM.Theme.getSize("thin_margin").height * 2 color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width @@ -52,289 +52,281 @@ Rectangle } ] - RowLayout - { - width: parent.width - UM.Theme.getSize("thin_margin").width * 2 - height: childrenRect.height + UM.Theme.getSize("thin_margin").height * 2 - x: UM.Theme.getSize("thin_margin").width - y: UM.Theme.getSize("thin_margin").height - spacing: UM.Theme.getSize("default_margin").width - Image //Separate column for icon on the left. + Image //Separate column for icon on the left. + { + id: packageItem + anchors { - Layout.preferredWidth: UM.Theme.getSize("card_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_icon").height - Layout.alignment: Qt.AlignTop + top: parent.top + left: parent.left + margins: UM.Theme.getSize("thin_margin").width + } + width: UM.Theme.getSize("card_icon").width + height: width - source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" + source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" + } + + Column + { + anchors + { + top: parent.top + left: packageItem.right + right: parent.right + margins: UM.Theme.getSize("thin_margin").width } - Column + RowLayout //Title row. { - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop + width: parent.width + spacing: UM.Theme.getSize("thin_margin").width + Label + { - RowLayout //Title row. + text: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } + + Control { - Layout.alignment: Qt.AlignTop - width: parent.width + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + //width: UM.Theme.getSize("card_tiny_icon").width + //height: UM.Theme.getSize("card_tiny_icon").height - Label - { - Layout.alignment: Qt.AlignTop + enabled: packageData.isVerified + visible: packageData.isVerified - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") + Cura.ToolTip + { + tooltipText: catalog.i18nc("@info", "Verified") + visible: parent.hovered } - Row //Row inside row, but the non-layout version skips invisible elements. + Rectangle { - spacing: parent.spacing - Layout.alignment: Qt.AlignTop - - Control + anchors.fill: parent + color: UM.Theme.getColor("action_button_hovered") + radius: width + UM.RecolorImage { - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignTop - - enabled: packageData.isVerified - visible: packageData.isVerified - - Cura.ToolTip - { - tooltipText: catalog.i18nc("@info", "Verified") - visible: parent.hovered - } - - Rectangle - { - anchors.fill: parent - color: UM.Theme.getColor("action_button_hovered") - radius: width - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") - } - } - - //NOTE: Can we link to something here? (Probably a static link explaining what verified is): - // onClicked: Qt.openUrlExternally( XXXXXX ) + anchors.fill: parent + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") } + } - Control - { - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignTop - - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered - } - - UM.RecolorImage - { - anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO - } + //NOTE: Can we link to something here? (Probably a static link explaining what verified is): + // onClicked: Qt.openUrlExternally( XXXXXX ) + } - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } + Control + { + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip + { + tooltipText: "" // TODO + visible: parent.hovered } - Label + UM.RecolorImage { - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop + anchors.fill: parent - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") // TODO } - UM.SimpleButton - { - id: externalLinkButton - - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignTop + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO + } - iconSource: UM.Theme.getIcon("LinkExternal") - hoverColor: UM.Theme.getColor("text_link") - backgroundColor: UM.Theme.getColor("detail_background") - hoverBackgroundColor: UM.Theme.getColor("action_button_hovered") - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } + Label + { + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true } - RowLayout + UM.SimpleButton { - id: downloadCountRow + id: externalLinkButton - width: childrenRect.width - height: childrenRect.height - x: UM.Theme.getSize("thin_margin").width - y: UM.Theme.getSize("thin_margin").height + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignTop - spacing: UM.Theme.getSize("thin_margin").width + iconSource: UM.Theme.getIcon("LinkExternal") + hoverColor: UM.Theme.getColor("text_link") + backgroundColor: UM.Theme.getColor("detail_background") + hoverBackgroundColor: UM.Theme.getColor("action_button_hovered") + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } + } - UM.RecolorImage - { - id: downloadCountIcon - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - color: UM.Theme.getColor("icon") + RowLayout + { + id: downloadCountRow - source: UM.Theme.getIcon("Download") - } + x: UM.Theme.getSize("thin_margin").width + y: UM.Theme.getSize("thin_margin").height - Label - { - id: downloadCountLabel - text: packageData.downloadCount - } + spacing: UM.Theme.getSize("thin_margin").width + + UM.RecolorImage + { + id: downloadCountIcon + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height + color: UM.Theme.getColor("icon") + + source: UM.Theme.getIcon("Download") } - Item + Label { - id: descriptionArea + id: downloadCountLabel + text: packageData.downloadCount + } + } + + Item + { + id: descriptionArea + width: parent.width + height: descriptionLabel.height + + Label + { + id: descriptionLabel width: parent.width - height: descriptionLabel.height + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + + text: packageData.description + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight - Label + onLineLaidOut: { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - - text: packageData.description - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight - - onLineLaidOut: + if(truncated && line.isLast) { - if(truncated && line.isLast) + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else - { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. - } - descriptionLabel.lastLineWidth = line.implicitWidth; + line.width = max_line_width; } + else + { + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. + } + descriptionLabel.lastLineWidth = line.implicitWidth; } } + } - Cura.TertiaryButton - { - id: readMoreButton - anchors.right: parent.right - anchors.bottom: parent.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true - - // NOTE: Is this the right URL for this action? - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: parent.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + // NOTE: Is this the right URL for this action? + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } - Label - { - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: readMoreButton.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated - } + Label + { + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: readMoreButton.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated } + } - RowLayout //Author and action buttons. + RowLayout //Author and action buttons. + { + width: parent.width + Layout.alignment: Qt.AlignTop + spacing: UM.Theme.getSize("thin_margin").width + + Label { - width: parent.width + id: authorBy Layout.alignment: Qt.AlignTop - spacing: UM.Theme.getSize("thin_margin").width - Label - { - id: authorBy - Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } + Cura.TertiaryButton + { + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true - Cura.SecondaryButton - { - id: disableButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required - } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } - Cura.SecondaryButton - { - id: uninstallButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required - } + Cura.SecondaryButton + { + id: disableButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Disable") + visible: false // not functional right now, also only when unfolding and required + } - Cura.PrimaryButton - { - id: installButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required - } + Cura.SecondaryButton + { + id: uninstallButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Uninstall") + visible: false // not functional right now, also only when unfolding and required + } + + Cura.PrimaryButton + { + id: installButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Update") // OR Download, if new! + visible: false // not functional right now, also only when unfolding and required } } } -- cgit v1.2.3 From c8491b47527faac24755853268aea524f92b5615 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 09:15:08 +0100 Subject: Remove background color from external link button CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 770731375f..3b549b2a38 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -52,8 +52,6 @@ Rectangle } ] - - Image //Separate column for icon on the left. { id: packageItem @@ -169,8 +167,6 @@ Rectangle iconSource: UM.Theme.getIcon("LinkExternal") hoverColor: UM.Theme.getColor("text_link") - backgroundColor: UM.Theme.getColor("detail_background") - hoverBackgroundColor: UM.Theme.getColor("action_button_hovered") onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } } -- cgit v1.2.3 From 4ae01df7f55e27ee0239800a7c24002695e4a6f6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 10:27:48 +0100 Subject: Ensure that the packagecard looks like the design CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 402 ++++++++++------------ 1 file changed, 186 insertions(+), 216 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 3b549b2a38..7465885c2b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,9 +12,8 @@ Rectangle { property var packageData - width: parent ? parent.width - UM.Theme.getSize("thin_margin").width : 0 - height: childrenRect.height + UM.Theme.getSize("thin_margin").height * 2 - + width: parent ? parent.width - UM.Theme.getSize("default_margin").width : 0 + height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width @@ -25,11 +24,6 @@ Rectangle name: "Folded" when: true // TODO PropertyChanges - { - target: downloadCountRow - visible: false - } - PropertyChanges { target: descriptionArea visible: true @@ -40,11 +34,6 @@ Rectangle name: "Header" when: false // TODO PropertyChanges - { - target: downloadCountRow - visible: true - } - PropertyChanges { target: descriptionArea visible: false @@ -59,7 +48,7 @@ Rectangle { top: parent.top left: parent.left - margins: UM.Theme.getSize("thin_margin").width + margins: UM.Theme.getSize("default_margin").width } width: UM.Theme.getSize("card_icon").width height: width @@ -67,263 +56,244 @@ Rectangle source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } - Column + RowLayout //Title row. { + id: titleBar anchors { - top: parent.top left: packageItem.right right: parent.right - margins: UM.Theme.getSize("thin_margin").width + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + leftMargin: UM.Theme.getSize("default_margin").width + rightMargin:UM.Theme.getSize("thick_margin").width } - RowLayout //Title row. + Label { - width: parent.width - spacing: UM.Theme.getSize("thin_margin").width - Label - { - - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignTop - } + text: packageData.displayName + font: UM.Theme.getFont("large_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - //width: UM.Theme.getSize("card_tiny_icon").width - //height: UM.Theme.getSize("card_tiny_icon").height + Control + { + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - enabled: packageData.isVerified - visible: packageData.isVerified - Cura.ToolTip - { - tooltipText: catalog.i18nc("@info", "Verified") - visible: parent.hovered - } + enabled: packageData.isVerified + visible: packageData.isVerified - Rectangle - { - anchors.fill: parent - color: UM.Theme.getColor("action_button_hovered") - radius: width - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") - } - } - - //NOTE: Can we link to something here? (Probably a static link explaining what verified is): - // onClicked: Qt.openUrlExternally( XXXXXX ) + Cura.ToolTip + { + tooltipText: catalog.i18nc("@info", "Verified") + visible: parent.hovered } - Control + Rectangle { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered - } - + anchors.fill: parent + color: UM.Theme.getColor("action_button_hovered") + radius: width UM.RecolorImage { anchors.fill: parent - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO + source: UM.Theme.getIcon("CheckCircle") } - - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO } - Label + //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 + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip { - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - Layout.fillWidth: true + tooltipText: "" // TODO + visible: parent.hovered } - UM.SimpleButton + UM.RecolorImage { - id: externalLinkButton - - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignTop + anchors.fill: parent - iconSource: UM.Theme.getIcon("LinkExternal") - hoverColor: UM.Theme.getColor("text_link") - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") // TODO } + + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO } - RowLayout + Label { - id: downloadCountRow - - x: UM.Theme.getSize("thin_margin").width - y: UM.Theme.getSize("thin_margin").height - - spacing: UM.Theme.getSize("thin_margin").width - - UM.RecolorImage - { - id: downloadCountIcon - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - color: UM.Theme.getColor("icon") + id: packageVersionLabel + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true + } - source: UM.Theme.getIcon("Download") - } + UM.SimpleButton + { + id: externalLinkButton - Label - { - id: downloadCountLabel - text: packageData.downloadCount - } + Layout.preferredWidth: packageVersionLabel.height + Layout.preferredHeight: packageVersionLabel.height + Layout.alignment: Qt.AlignTop + iconSource: UM.Theme.getIcon("LinkExternal") + hoverColor: UM.Theme.getColor("text_link") + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } + } - Item + Item + { + id: descriptionArea + height: descriptionLabel.height + anchors + { + top: titleBar.bottom + left: packageItem.right + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + leftMargin: UM.Theme.getSize("default_margin").width + } + Label { - id: descriptionArea + id: descriptionLabel width: parent.width - height: descriptionLabel.height + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - Label + text: packageData.description + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - - text: packageData.description - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight - - onLineLaidOut: + if(truncated && line.isLast) { - if(truncated && line.isLast) + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) + { + line.width = max_line_width; + } + else { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else - { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. - } - descriptionLabel.lastLineWidth = line.implicitWidth; + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. } + descriptionLabel.lastLineWidth = line.implicitWidth; } } + } + Label + { + id: tripleDotLabel + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: readMoreButton.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated + } + Cura.TertiaryButton + { + id: readMoreButton + anchors.left: tripleDotLabel.right + anchors.bottom: parent.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + // NOTE: Is this the right URL for this action? + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } + } - Cura.TertiaryButton - { - id: readMoreButton - anchors.right: parent.right - anchors.bottom: parent.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true - - // NOTE: Is this the right URL for this action? - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } - - Label - { - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: readMoreButton.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated - } + RowLayout //Author and action buttons. + { + id: authorAndACtionButton + width: parent.width + anchors + { + bottom: parent.bottom + left: packageItem.right + margins: UM.Theme.getSize("default_margin").height } + spacing: UM.Theme.getSize("narrow_margin").width - RowLayout //Author and action buttons. + Label { - width: parent.width + id: authorBy Layout.alignment: Qt.AlignTop - spacing: UM.Theme.getSize("thin_margin").width - Label - { - id: authorBy - Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } + Cura.TertiaryButton + { + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true - Cura.SecondaryButton - { - id: disableButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required - } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } - Cura.SecondaryButton - { - id: uninstallButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required - } + Cura.SecondaryButton + { + id: disableButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Disable") + visible: false // not functional right now, also only when unfolding and required + } - Cura.PrimaryButton - { - id: installButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required - } + Cura.SecondaryButton + { + id: uninstallButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Uninstall") + visible: false // not functional right now, also only when unfolding and required + } + + Cura.PrimaryButton + { + id: installButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Update") // OR Download, if new! + visible: false // not functional right now, also only when unfolding and required } } -- cgit v1.2.3 From 5a08ae0eab7ea95432cf2d7e0d1e5469b8bf8ecc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 10:52:33 +0100 Subject: Give the poor + a bit more breathing room It's a pandemic, we need to give operators a bit of room ;) --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 0dc33d62f3..1d23b3296a 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -10,7 +10,7 @@ import QtQuick.Controls 2.15 TabButton { id: root - width: UM.Theme.getSize("button_icon").width+UM.Theme.getSize("narrow_margin").width + width: UM.Theme.getSize("button_icon").width + UM.Theme.getSize("narrow_margin").width height: UM.Theme.getSize("button_icon").height hoverEnabled: true property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background") -- cgit v1.2.3 From d7e023c5ee83e3b0ce87c734e747bad078a0064e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 10:56:06 +0100 Subject: Fix sizes of icons in package card CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- resources/themes/cura-light/theme.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 7465885c2b..5c1b0fc5fa 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -148,8 +148,8 @@ Rectangle { id: externalLinkButton - Layout.preferredWidth: packageVersionLabel.height - Layout.preferredHeight: packageVersionLabel.height + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height Layout.alignment: Qt.AlignTop iconSource: UM.Theme.getIcon("LinkExternal") hoverColor: UM.Theme.getColor("text_link") diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index a4f3172036..81885faaf0 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -556,7 +556,7 @@ "card": [25.0, 8.75], "card_icon": [6.0, 6.0], - "card_tiny_icon": [1.2, 1.2], + "card_tiny_icon": [1.5, 1.5], "button": [4, 4], "button_icon": [2.5, 2.5], -- cgit v1.2.3 From f01ce5b43c54ff4900f809d9517d12439b1aaab0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 10:59:31 +0100 Subject: Fix direction of tooltip point CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 5c1b0fc5fa..8177cac19d 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -90,6 +90,7 @@ Rectangle { tooltipText: catalog.i18nc("@info", "Verified") visible: parent.hovered + targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) } Rectangle -- cgit v1.2.3 From 5ac0df8b0f7d674b757ceb2433a55b9c44c01af2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 11:22:30 +0100 Subject: Fix styling of scrollbar in marketplace CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- plugins/Marketplace/resources/qml/Packages.qml | 282 ++++++++++++---------- 2 files changed, 150 insertions(+), 134 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 8177cac19d..84015d73be 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,7 @@ Rectangle { property var packageData - width: parent ? parent.width - UM.Theme.getSize("default_margin").width : 0 + width: parent ? parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width: 0 height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 95064c4469..55bde0f58b 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -5,181 +5,197 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import UM 1.4 as UM -ScrollView + +ListView { id: packages - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - property alias model: packagesListview.model property string pageTitle + width: parent.width + + clip: true Component.onCompleted: model.updatePackages() Component.onDestruction: model.abortUpdating() - ListView - { - id: packagesListview - width: parent.width + //ScrollBar.vertical.policy: ScrollBar.AlwaysOff - spacing: UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("default_margin").height - section.property: "package.sectionTitle" - section.delegate: Rectangle - { - width: packagesListview.width - height: sectionHeaderText.height + UM.Theme.getSize("default_margin").height + section.property: "package.sectionTitle" + section.delegate: Rectangle + { + width: packages.width + height: sectionHeaderText.height + UM.Theme.getSize("default_margin").height - color: UM.Theme.getColor("detail_background") + color: UM.Theme.getColor("detail_background") - required property string section + required property string section - Label - { - id: sectionHeaderText - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left + Label + { + id: sectionHeaderText + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left - text: parent.section - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - } + text: parent.section + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") } + } + + ScrollBar.vertical: ScrollBar + { + // Vertical ScrollBar, styled similarly to the scrollBar in the settings panel + id: verticalScrollBar + visible: packages.contentHeight > packages.height - delegate: PackageCard + background: Item{} + + contentItem: Rectangle { - packageData: model.package + id: scrollViewHandle + implicitWidth: UM.Theme.getSize("scrollbar").width + radius: Math.round(implicitWidth / 2) + color: verticalScrollBar.pressed ? UM.Theme.getColor("scrollbar_handle_down") : verticalScrollBar.hovered ? UM.Theme.getColor("scrollbar_handle_hover") : UM.Theme.getColor("scrollbar_handle") + Behavior on color { ColorAnimation { duration: 50; } } } + } - //Wrapper item to add spacing between content and footer. - footer: Item + delegate: PackageCard + { + packageData: model.package + } + + //Wrapper item to add spacing between content and footer. + footer: Item + { + width: parent.width + height: model.hasFooter || packages.model.errorMessage != "" ? UM.Theme.getSize("card").height + packages.spacing : 0 + visible: model.hasFooter || packages.model.errorMessage != "" + Button { + id: loadMoreButton width: parent.width - height: model.hasFooter || packages.model.errorMessage != "" ? UM.Theme.getSize("card").height + packagesListview.spacing : 0 - visible: model.hasFooter || packages.model.errorMessage != "" - Button - { - id: loadMoreButton - width: parent.width - height: UM.Theme.getSize("card").height - anchors.bottom: parent.bottom + height: UM.Theme.getSize("card").height + anchors.bottom: parent.bottom - enabled: packages.model.hasMore && !packages.model.isLoading || packages.model.errorMessage != "" - onClicked: packages.model.updatePackages() //Load next page in plug-in list. + enabled: packages.model.hasMore && !packages.model.isLoading || packages.model.errorMessage != "" + onClicked: packages.model.updatePackages() //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") - } + background: Rectangle + { + anchors.fill: parent + radius: UM.Theme.getSize("default_radius").width + color: UM.Theme.getColor("main_background") + } - Row - { - anchors.centerIn: parent + Row + { + anchors.centerIn: parent - spacing: UM.Theme.getSize("thin_margin").width + spacing: UM.Theme.getSize("thin_margin").width - states: - [ - State + states: + [ + State + { + name: "Error" + when: packages.model.errorMessage != "" + PropertyChanges { - 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 packages:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") - } - }, - State + target: errorIcon + visible: true + } + PropertyChanges { - 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 + target: loadMoreIcon + visible: false + } + PropertyChanges { - 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") - } + target: loadMoreLabel + text: catalog.i18nc("@button", "Failed to load packages:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") } - ] - - Item + }, + State { - 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 + name: "Loading" + when: packages.model.isLoading + PropertyChanges { - id: errorIcon - anchors.fill: parent - - status: UM.StatusIcon.Status.ERROR + 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 } - UM.RecolorImage + PropertyChanges { - 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 - } + target: loadMoreLabel + text: catalog.i18nc("@button", "No more results to load") + color: UM.Theme.getColor("action_button_disabled_text") } } - Label + ] + + 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: loadMoreLabel - text: catalog.i18nc("@button", "Load more") - font: UM.Theme.getFont("medium_bold") + 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") + } } } } } + -- cgit v1.2.3 From f6b351f97f0547b5f12b8ee47e16dee55e689b64 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 11:22:57 +0100 Subject: Fix size of footer CURA-8561 --- plugins/Marketplace/resources/qml/Packages.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 55bde0f58b..5a7f56748a 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -70,7 +70,7 @@ ListView //Wrapper item to add spacing between content and footer. footer: Item { - width: parent.width + width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width height: model.hasFooter || packages.model.errorMessage != "" ? UM.Theme.getSize("card").height + packages.spacing : 0 visible: model.hasFooter || packages.model.errorMessage != "" Button -- cgit v1.2.3 From 183fa06cfe564a87ca6f72af945413aa5963f158 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 11:54:12 +0100 Subject: Fix look of external link button CURA-8561 --- plugins/Marketplace/resources/qml/PackageCard.qml | 30 +++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 84015d73be..69b1639cc8 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -145,17 +145,33 @@ Rectangle Layout.fillWidth: true } - UM.SimpleButton + Button { id: externalLinkButton - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignTop - iconSource: UM.Theme.getIcon("LinkExternal") - hoverColor: UM.Theme.getColor("text_link") - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: UM.Theme.getSize("narrow_margin").width + topPadding: UM.Theme.getSize("narrow_margin").width + bottomPadding: UM.Theme.getSize("narrow_margin").width + + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + contentItem: UM.RecolorImage + { + source: UM.Theme.getIcon("LinkExternal") + color: UM.Theme.getColor("icon") + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + } + + background: Rectangle + { + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" + radius: externalLinkButton.width / 2 + } } + } Item -- cgit v1.2.3 From 07e9237d7202c835439156cff650c0295b33d469 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Nov 2021 14:23:26 +0100 Subject: Add missing action for button CURA-8561 --- plugins/Marketplace/PackageModel.py | 1 - plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 0e73cac3b7..f87a30e0b7 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -27,7 +27,6 @@ class PackageModel(QObject): """ super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") - self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) self._is_verified = "verified" in package_data.get("tags", []) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 69b1639cc8..56b3519360 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -170,6 +170,7 @@ Rectangle color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" radius: externalLinkButton.width / 2 } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } } @@ -245,7 +246,6 @@ Rectangle textFont: descriptionLabel.font isIconOnRightSide: true - // NOTE: Is this the right URL for this action? onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } } -- cgit v1.2.3 From 307d751c2d47fc1805d4058d6659d7e75ddeb1e7 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 18 Nov 2021 08:14:04 +0100 Subject: Fix white rectangles in dark mode and height of search-bar. part of CURA-8559 --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 124bc7406b..c04aa7eb6a 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -85,6 +85,7 @@ Window Rectangle { + color: "transparent" Layout.preferredHeight: parent.height Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 Layout.fillWidth: ! searchBar.visible @@ -93,7 +94,7 @@ Window Cura.SearchBar { id: searchBar - Layout.preferredHeight: parent.height + Layout.preferredHeight: UM.Theme.getSize("button_icon").height Layout.fillWidth: true onTextEdited: searchStringChanged(text) } @@ -105,6 +106,7 @@ Window anchors.right: parent.right height: UM.Theme.getSize("button_icon").height spacing: 0 + background: Rectangle { color: "transparent" } PackageTypeTab { -- cgit v1.2.3 From cd255b0ae52a8238b8718df1354f4c142ead4aac Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 18 Nov 2021 08:15:26 +0100 Subject: Search Marketplace: Change message if no results found. part of CURA-8559 --- plugins/Marketplace/resources/qml/Packages.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 5a7f56748a..79b4bf23a5 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -147,7 +147,7 @@ ListView PropertyChanges { target: loadMoreLabel - text: catalog.i18nc("@button", "No more results to load") + text: packages.model.count > 0 ? catalog.i18nc("@message", "No more results to load") : catalog.i18nc("@message", "No results found with current filter") color: UM.Theme.getColor("action_button_disabled_text") } } -- cgit v1.2.3 From dcb94568481b7858716354e9379c2e1249babeab Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 18 Nov 2021 08:44:06 +0100 Subject: Fix CApitalization and spurious space. part of CURA-8561 --- plugins/Marketplace/resources/qml/ManagePackagesButton.qml | 2 +- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml index 1d23b3296a..92e2196beb 100644 --- a/plugins/Marketplace/resources/qml/ManagePackagesButton.qml +++ b/plugins/Marketplace/resources/qml/ManagePackagesButton.qml @@ -43,7 +43,7 @@ TabButton color: UM.Theme.getColor("icon") source: UM.Theme.getIcon("Settings") anchors.horizontalCenter: parent.horizontalCenter - anchors.horizontalCenterOffset: Math.round(UM.Theme.getSize("narrow_margin").width /2) + anchors.horizontalCenterOffset: Math.round(UM.Theme.getSize("narrow_margin").width /2) anchors.verticalCenter: parent.verticalCenter } } diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 56b3519360..5ce24f8dee 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -252,7 +252,7 @@ Rectangle RowLayout //Author and action buttons. { - id: authorAndACtionButton + id: authorAndActionButton width: parent.width anchors { -- cgit v1.2.3 From 584411e59e485e159b58b9d09a49e916a26073fa Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 18 Nov 2021 13:04:19 +0100 Subject: Moved comments if they're inline they mess up the QML syntax highlighter in PyCharm --- plugins/Marketplace/resources/qml/PackageCard.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 5ce24f8dee..6387a9d893 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -41,7 +41,8 @@ Rectangle } ] - Image //Separate column for icon on the left. + // Separate column for icon on the left. + Image { id: packageItem anchors @@ -56,7 +57,8 @@ Rectangle source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } - RowLayout //Title row. + // Title row. + RowLayout { id: titleBar anchors @@ -250,7 +252,8 @@ Rectangle } } - RowLayout //Author and action buttons. + // Author and action buttons. + RowLayout { id: authorAndActionButton width: parent.width -- cgit v1.2.3 From eb3083c84d5dacf39dbc9ae8c38cbc55649a146a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 18 Nov 2021 18:44:55 +0100 Subject: use differentiate icon for Ultimaker controlled material/plugin Materials can be certified, while plugins can be verified. Added packageType to the model such that the card knows which icon to use. Contributes to CURA-8562 --- plugins/Marketplace/PackageModel.py | 14 ++++++++++---- plugins/Marketplace/resources/qml/PackageCard.qml | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index f87a30e0b7..3bdf6fbada 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -27,9 +27,11 @@ class PackageModel(QObject): """ super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") + self._package_type = package_data.get("package_type", "") self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) - self._is_verified = "verified" in package_data.get("tags", []) + tags = package_data.get("tags", []) + self._is_checked_by_ultimaker = "verified" in tags and self._package_type == "plugin" or "certified" in tags and self._package_type == "material" self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. self._download_count = package_data.get("download_count", 0) @@ -49,6 +51,10 @@ class PackageModel(QObject): def packageId(self) -> str: return self._package_id + @pyqtProperty(str, constant = True) + def packageType(self) -> str: + return self._package_type + @pyqtProperty(str, constant=True) def iconUrl(self): return self._icon_url @@ -57,9 +63,9 @@ class PackageModel(QObject): def displayName(self) -> str: return self._display_name - @pyqtProperty(bool, constant=True) - def isVerified(self): - return self._is_verified + @pyqtProperty(bool, constant = True) + def isCheckedByUltimaker(self): + return self._is_checked_by_ultimaker @pyqtProperty(str, constant=True) def packageVersion(self): diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 6387a9d893..58047ceb4e 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -85,12 +85,12 @@ Rectangle Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - enabled: packageData.isVerified - visible: packageData.isVerified + enabled: packageData.isCheckedByUltimaker + visible: packageData.isCheckedByUltimaker Cura.ToolTip { - tooltipText: catalog.i18nc("@info", "Verified") + tooltipText: packageData.packageType == "plugin" ? catalog.i18nc("@info", "Verified") : catalog.i18nc("@info", "Ultimaker Certified Materials") visible: parent.hovered targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) } @@ -104,7 +104,7 @@ Rectangle { anchors.fill: parent color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") + source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") } } -- cgit v1.2.3 From 7fd9578599b777cdd66b88197360ce9f6e4f1aff Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 18 Nov 2021 18:49:54 +0100 Subject: Unified tooltip description Contributes to CURA-8562 --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 58047ceb4e..64aedc4c4a 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -90,7 +90,7 @@ Rectangle Cura.ToolTip { - tooltipText: packageData.packageType == "plugin" ? catalog.i18nc("@info", "Verified") : catalog.i18nc("@info", "Ultimaker Certified Materials") + tooltipText: packageData.packageType == "plugin" ? catalog.i18nc("@info", "Ultimaker Verified Plugin") : catalog.i18nc("@info", "Ultimaker Certified Material") visible: parent.hovered targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) } -- cgit v1.2.3 From 05c0857cb2b2267eff1add55b50aed150182af4e Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 19 Nov 2021 10:57:04 +0100 Subject: Add Certified icon Contributes to CURA-8562 --- resources/themes/cura-light/icons/default/Certified.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/Certified.svg diff --git a/resources/themes/cura-light/icons/default/Certified.svg b/resources/themes/cura-light/icons/default/Certified.svg new file mode 100644 index 0000000000..3d1ae19b60 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Certified.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From 35ba8f78a07d2372f731741ca94d7675fd87a422 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 Nov 2021 17:10:56 +0100 Subject: Disambiguate associativity of and/or operators It's the same, but it's a bit easier to read what it's doing. Placing the package type check first also helps to read what the two cases are here. Contributes to issue CURA-8562. --- plugins/Marketplace/PackageModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 3bdf6fbada..e83364068c 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -31,7 +31,7 @@ class PackageModel(QObject): self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) tags = package_data.get("tags", []) - self._is_checked_by_ultimaker = "verified" in tags and self._package_type == "plugin" or "certified" in tags and self._package_type == "material" + self._is_checked_by_ultimaker = (self._package_type == "plugin" and "verified" in tags) or (self._package_type == "material" and "certified" in tags) self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. self._download_count = package_data.get("download_count", 0) -- cgit v1.2.3 From bddcf3cb0cefd8caa0cff5451114d22f90d1cac4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 Nov 2021 17:16:39 +0100 Subject: Add extensible switch case for tooltip This way it's - more clear what the two cases are - and more robust if we ever add a third. Contributes to issue CURA-8562. --- plugins/Marketplace/resources/qml/PackageCard.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 64aedc4c4a..433b77a54d 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -90,7 +90,15 @@ Rectangle Cura.ToolTip { - tooltipText: packageData.packageType == "plugin" ? catalog.i18nc("@info", "Ultimaker Verified Plugin") : catalog.i18nc("@info", "Ultimaker Certified Material") + 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 / 2)) } -- cgit v1.2.3 From 64dba8374a3d1359e70c40c212dbaa1a2fd3fc34 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 Nov 2021 17:17:24 +0100 Subject: Remove fill colours The colour gets determined by the theme. Contributes to issue CURA-8562. --- resources/themes/cura-light/icons/default/Certified.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/themes/cura-light/icons/default/Certified.svg b/resources/themes/cura-light/icons/default/Certified.svg index 3d1ae19b60..031011213a 100644 --- a/resources/themes/cura-light/icons/default/Certified.svg +++ b/resources/themes/cura-light/icons/default/Certified.svg @@ -1,3 +1,3 @@ - - + + -- cgit v1.2.3 From b98e0d17532c9aff05d8315a07176c0bbb2c7384 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 23 Nov 2021 13:46:33 +0100 Subject: Use author icon if no package icon known. part of CURA-8562 --- plugins/Marketplace/PackageModel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index e83364068c..93d73bbc83 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -43,6 +43,8 @@ class PackageModel(QObject): author_data = package_data.get("author", {}) self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) self._author_info_url = author_data.get("website", "") + if not self._icon_url or self._icon_url == "": + self._icon_url = author_data.get("icon_url", "") self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. -- cgit v1.2.3 From 853f915f2a1fabfeb8dbe4fce2edcc1bd1c063ad Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 10:06:50 +0100 Subject: Add basic onboarding banner to the market place --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++ .../Marketplace/resources/qml/OnboardBanner.qml | 79 ++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 plugins/Marketplace/resources/qml/OnboardBanner.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index c04aa7eb6a..3fcc0bb3fd 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -48,6 +48,10 @@ Window spacing: UM.Theme.getSize("default_margin").height + OnboardBanner + { + } + // Page title. Item { diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml new file mode 100644 index 0000000000..b91a9a52f7 --- /dev/null +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -0,0 +1,79 @@ +// 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 + +// Onboarding banner. +Rectangle +{ + Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height + anchors + { + margins: UM.Theme.getSize("default_margin").width + left: parent.left + right: parent.right + top: parent.top + } + + color: UM.Theme.getColor("action_panel_secondary") + + // Icon + Rectangle + { + id: onboardingIcon + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("button_icon").width + height: UM.Theme.getSize("button_icon").height + color: "transparent" + UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary_text") + source: UM.Theme.getIcon("Shop") + } + } + + // Close button + UM.SimpleButton + { + id: onboardingClose + anchors + { + top: parent.top + right: parent.right + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("message_close").width + height: UM.Theme.getSize("message_close").height + color: UM.Theme.getColor("primary_text") + hoverColor: UM.Theme.getColor("primary_text_hover") + iconSource: UM.Theme.getIcon("Cancel") + onClicked: confirmDeleteDialog.visible = true + } + + // Body + Text { + anchors + { + top: parent.top + left: onboardingIcon.right + right: onboardingClose.left + margins: UM.Theme.getSize("default_margin").width + } + + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("primary_text") + wrapMode: Text.WordWrap + text: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + } +} \ No newline at end of file -- cgit v1.2.3 From 20f94add47835ace7d5c16d37ff88ef1f6318d9f Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 10:10:09 +0100 Subject: Display different content on each of the marketplace onboarding banners --- .../Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Marketplace.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/OnboardBanner.qml | 20 ++++++++++++++++++-- plugins/Marketplace/resources/qml/Packages.qml | 2 ++ plugins/Marketplace/resources/qml/Plugins.qml | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 243d5bf12e..c53384bf77 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -10,6 +10,7 @@ import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Manage packages") + bannerType: "__MANAGE_PACKAGES__" model: Marketplace.LocalPackageList { } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 3fcc0bb3fd..7293a61a80 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -50,6 +50,7 @@ Window OnboardBanner { + bannerType: content.item && content.item.bannerType } // Page title. diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 1d1572976a..dff63305bf 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -6,6 +6,7 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Materials") + bannerType: "__MATERIALS__" model: Marketplace.RemotePackageList { packageTypeFilter: "material" diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index b91a9a52f7..8a1048018c 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,6 +11,8 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { + property var bannerType + Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors { @@ -39,7 +41,14 @@ Rectangle { anchors.fill: parent color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon("Shop") + source: { + switch (bannerType) { + case "__PLUGINS__" : return UM.Theme.getIcon("Shop"); + case "__MATERIALS__" : return UM.Theme.getIcon("Spool"); + case "__MANAGE_PACKAGES__" : return UM.Theme.getIcon("ArrowDoubleCircleRight"); + default: return ""; + } + } } } @@ -74,6 +83,13 @@ Rectangle font: UM.Theme.getFont("medium") color: UM.Theme.getColor("primary_text") wrapMode: Text.WordWrap - text: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + text: { + switch (bannerType) { + case "__PLUGINS__" : return catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users."); + case "__MATERIALS__" : return catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers."); + case "__MANAGE_PACKAGES__" : return catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly."); + default: return ""; + } + } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 79b4bf23a5..345aecf822 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -11,6 +11,8 @@ ListView id: packages property string pageTitle + property string bannerType + width: parent.width clip: true diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index ef5d92c2e8..24381e3027 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -6,6 +6,7 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") + bannerType: "__PLUGINS__" model: Marketplace.RemotePackageList { packageTypeFilter: "plugin" -- cgit v1.2.3 From 60b174177fc887552ba00f5e766c9c87366e919e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Nov 2021 14:21:18 +0100 Subject: Also set correct size for package card title --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 433b77a54d..63909ef049 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -66,7 +66,7 @@ Rectangle left: packageItem.right right: parent.right top: parent.top - topMargin: UM.Theme.getSize("default_margin").height + topMargin: UM.Theme.getSize("narrow_margin").height leftMargin: UM.Theme.getSize("default_margin").width rightMargin:UM.Theme.getSize("thick_margin").width } @@ -74,7 +74,7 @@ Rectangle Label { text: packageData.displayName - font: UM.Theme.getFont("large_bold") + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") verticalAlignment: Text.AlignTop } -- cgit v1.2.3 From 09a569fdd1fa416367d47f7b68eb2bc7443b149f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Nov 2021 14:14:43 +0100 Subject: Change fonts to default No idea why they were medium, but the design clearly shows that it should be the default font --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 63909ef049..e80b352978 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -204,7 +204,7 @@ Rectangle property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") maximumLineCount: 2 wrapMode: Text.Wrap @@ -328,6 +328,6 @@ Rectangle FontMetrics { id: fontMetrics - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") } } -- cgit v1.2.3 From 3ddfa6486b3d45ba16be1e059c2d6613ce905bde Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 14:49:00 +0100 Subject: Remove banners when clicking close button --- cura/CuraApplication.py | 31 ++++++++++++++++++++++ .../Marketplace/resources/qml/ManagedPackages.qml | 10 ++++++- plugins/Marketplace/resources/qml/Marketplace.qml | 5 +++- plugins/Marketplace/resources/qml/Materials.qml | 10 ++++++- .../Marketplace/resources/qml/OnboardBanner.qml | 28 +++++++------------ plugins/Marketplace/resources/qml/Packages.qml | 5 +++- plugins/Marketplace/resources/qml/Plugins.qml | 10 ++++++- 7 files changed, 76 insertions(+), 23 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3d4ec1209f..6cf2593bbf 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -572,6 +572,10 @@ class CuraApplication(QtApplication): preferences.addPreference("general/accepted_user_agreement", False) + preferences.addPreference("cura/market_place_show_plugin_banner", True) + preferences.addPreference("cura/market_place_show_material_banner", True) + preferences.addPreference("cura/market_place_show_manage_packages_banner", True) + for key in [ "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_profile_path", @@ -2011,6 +2015,33 @@ class CuraApplication(QtApplication): show_whatsnew_only = has_active_machine and has_app_just_upgraded return show_whatsnew_only + @pyqtSlot(result = bool) + def shouldShowMarketPlacePluginBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_plugin_banner") + + @pyqtSlot(result = bool) + def shouldShowMarketPlaceMaterialBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_material_banner") + + @pyqtSlot(result = bool) + def shouldShowMarketPlaceManagePackagesBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_manage_packages_banner") + + @pyqtSlot() + def closeMarketPlacePluginBanner(self) -> None: + Logger.log("i", "Close market place plugin banner") + self._preferences.setValue("cura/market_place_show_plugin_banner", False) + + @pyqtSlot() + def closeMarketPlaceMaterialBanner(self) -> None: + Logger.log("i", "Close market place material banner") + self._preferences.setValue("cura/market_place_show_material_banner", False) + + @pyqtSlot() + def closeMarketPlaceManagePackagesBanner(self) -> None: + Logger.log("i", "Close market place manage packages banner") + self._preferences.setValue("cura/market_place_show_manage_packages_banner", False) + @pyqtSlot(result = int) def appWidth(self) -> int: main_window = QtApplication.getInstance().getMainWindow() diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index c53384bf77..a329d992e5 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -10,7 +10,15 @@ import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Manage packages") - bannerType: "__MANAGE_PACKAGES__" + + bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() + bannerIcon: "ArrowDoubleCircleRight" + bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlaceManagePackagesBanner(); + bannerVisible = false; + } + model: Marketplace.LocalPackageList { } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 7293a61a80..14de458e95 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -50,7 +50,10 @@ Window OnboardBanner { - bannerType: content.item && content.item.bannerType + bannerVisible: content.item && content.item.bannerVisible + bannerBody: content.item && content.item.bannerBody + bannerIcon: content.item && content.item.bannerIcon + onRemoveBanner: content.item && content.item.onRemoveBanner } // Page title. diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index dff63305bf..32d2c2213a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -6,7 +6,15 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Materials") - bannerType: "__MATERIALS__" + + bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() + bannerIcon: "Spool" + bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlaceMaterialBanner(); + bannerVisible = false; + } + model: Marketplace.RemotePackageList { packageTypeFilter: "material" diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8a1048018c..8a29030514 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,7 +11,12 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property var bannerType + property bool bannerVisible + property string bannerIcon + property string bannerBody + property var onRemoveBanner + + visible: bannerVisible Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors @@ -41,14 +46,7 @@ Rectangle { anchors.fill: parent color: UM.Theme.getColor("primary_text") - source: { - switch (bannerType) { - case "__PLUGINS__" : return UM.Theme.getIcon("Shop"); - case "__MATERIALS__" : return UM.Theme.getIcon("Spool"); - case "__MANAGE_PACKAGES__" : return UM.Theme.getIcon("ArrowDoubleCircleRight"); - default: return ""; - } - } + source: UM.Theme.getIcon(bannerIcon) } } @@ -67,7 +65,8 @@ Rectangle color: UM.Theme.getColor("primary_text") hoverColor: UM.Theme.getColor("primary_text_hover") iconSource: UM.Theme.getIcon("Cancel") - onClicked: confirmDeleteDialog.visible = true + + onClicked: onRemoveBanner() } // Body @@ -83,13 +82,6 @@ Rectangle font: UM.Theme.getFont("medium") color: UM.Theme.getColor("primary_text") wrapMode: Text.WordWrap - text: { - switch (bannerType) { - case "__PLUGINS__" : return catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users."); - case "__MATERIALS__" : return catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers."); - case "__MANAGE_PACKAGES__" : return catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly."); - default: return ""; - } - } + text: bannerBody } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 345aecf822..91f6448994 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -11,7 +11,10 @@ ListView id: packages property string pageTitle - property string bannerType + property bool bannerVisible + property string bannerIcon + property string bannerBody + property var onRemoveBanner width: parent.width diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 24381e3027..b9d38c6e2b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -6,7 +6,15 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") - bannerType: "__PLUGINS__" + + bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() + bannerIcon: "Shop" + bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlacePluginBanner(); + bannerVisible = false; + } + model: Marketplace.RemotePackageList { packageTypeFilter: "plugin" -- cgit v1.2.3 From a715274ca7336e8032675e891bc6224dced00dc4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 26 Nov 2021 16:04:23 +0100 Subject: Add a StackView around Marketplace to allow extra pages on top This allows a sort of full-screen pop-up to replace the entire Marketplace window contents, on top of the normal contents. The normal contents are kept as they are, but out of view. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 220 +++++++++++---------- .../Marketplace/resources/qml/PackageDetails.qml | 10 + plugins/Marketplace/resources/qml/Packages.qml | 15 +- 3 files changed, 138 insertions(+), 107 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackageDetails.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index c04aa7eb6a..b293d21f92 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -42,143 +42,153 @@ Window anchors.fill: parent color: UM.Theme.getColor("main_background") - ColumnLayout + //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. + StackView { + id: contextStack anchors.fill: parent - spacing: UM.Theme.getSize("default_margin").height + initialItem: packageBrowse - // Page title. - Item + ColumnLayout { - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + id: packageBrowse + anchors.fill: parent - Label + spacing: UM.Theme.getSize("default_margin").height + + // Page title. + Item { - id: pageTitle - anchors + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + + Label { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - bottom: parent.bottom - } + id: pageTitle + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + bottom: parent.bottom + } - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + } } - } - // Search & Top-Level Tabs - Item - { - Layout.preferredHeight: childrenRect.height - Layout.preferredWidth: parent.width - 2 * UM.Theme.getSize("thin_margin").width - RowLayout + // Search & Top-Level Tabs + Item { - width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height - spacing: UM.Theme.getSize("thin_margin").width - - Rectangle + Layout.preferredHeight: childrenRect.height + Layout.preferredWidth: parent.width - 2 * UM.Theme.getSize("thin_margin").width + RowLayout { - color: "transparent" - Layout.preferredHeight: parent.height - Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 - Layout.fillWidth: ! searchBar.visible - } + width: parent.width + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("thin_margin").width - Cura.SearchBar - { - id: searchBar - Layout.preferredHeight: UM.Theme.getSize("button_icon").height - Layout.fillWidth: true - onTextEdited: searchStringChanged(text) - } + Rectangle + { + color: "transparent" + Layout.preferredHeight: parent.height + Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 + Layout.fillWidth: ! searchBar.visible + } - // Page selection. - TabBar - { - id: pageSelectionTabBar - anchors.right: parent.right - height: UM.Theme.getSize("button_icon").height - spacing: 0 - background: Rectangle { color: "transparent" } + Cura.SearchBar + { + id: searchBar + Layout.preferredHeight: UM.Theme.getSize("button_icon").height + Layout.fillWidth: true + onTextEdited: searchStringChanged(text) + } - PackageTypeTab + // Page selection. + TabBar { - id: pluginTabText - width: implicitWidth - text: catalog.i18nc("@button", "Plugins") - onClicked: + id: pageSelectionTabBar + anchors.right: parent.right + height: UM.Theme.getSize("button_icon").height + spacing: 0 + background: Rectangle { color: "transparent" } + + PackageTypeTab { - searchBar.text = "" - searchBar.visible = true - content.source = "Plugins.qml" + id: pluginTabText + width: implicitWidth + text: catalog.i18nc("@button", "Plugins") + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Plugins.qml" + } } - } - PackageTypeTab - { - id: materialsTabText - width: implicitWidth - text: catalog.i18nc("@button", "Materials") - onClicked: + PackageTypeTab { - searchBar.text = "" - searchBar.visible = true - content.source = "Materials.qml" + id: materialsTabText + width: implicitWidth + text: catalog.i18nc("@button", "Materials") + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Materials.qml" + } + } + ManagePackagesButton + { + onClicked: content.source = "ManagedPackages.qml" } } - ManagePackagesButton + + TextMetrics { - onClicked: content.source = "ManagedPackages.qml" + id: pluginTabTextMetrics + text: pluginTabText.text + font: pluginTabText.font + } + TextMetrics + { + id: materialsTabTextMetrics + text: materialsTabText.text + font: materialsTabText.font } } - - TextMetrics - { - id: pluginTabTextMetrics - text: pluginTabText.text - font: pluginTabText.font - } - TextMetrics - { - id: materialsTabTextMetrics - text: materialsTabText.text - font: materialsTabText.font - } } - } - - // Page contents. - Rectangle - { - Layout.preferredWidth: parent.width - Layout.fillHeight: true - color: UM.Theme.getColor("detail_background") // Page contents. - Loader + Rectangle { - id: content - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width - source: "Plugins.qml" + Layout.preferredWidth: parent.width + Layout.fillHeight: true + color: UM.Theme.getColor("detail_background") - Connections + // Page contents. + Loader { - target: content - function onLoaded() - { - pageTitle.text = content.item.pageTitle - searchStringChanged.connect(handleSearchStringChanged) - } - function handleSearchStringChanged(new_search) + id: content + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + source: "Plugins.qml" + + Connections { - content.item.model.searchString = new_search + target: content + function onLoaded() + { + pageTitle.text = content.item.pageTitle + searchStringChanged.connect(handleSearchStringChanged) + } + function handleSearchStringChanged(new_search) + { + content.item.model.searchString = new_search + } } } } diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml new file mode 100644 index 0000000000..4ad376e490 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -0,0 +1,10 @@ +// 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 + +Label +{ + text: "Test!" +} \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 79b4bf23a5..def8d40acb 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -62,9 +62,20 @@ ListView } } - delegate: PackageCard + delegate: MouseArea { - packageData: model.package + width: parent.width + height: childrenRect.height + + onClicked: + { + contextStack.push(Qt.resolvedUrl("PackageDetails.qml")) + } + + PackageCard + { + packageData: model.package + } } //Wrapper item to add spacing between content and footer. -- cgit v1.2.3 From 0d05e71f9da49cfdeb1b2b4af7bf9da2144f4dee Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 26 Nov 2021 16:48:02 +0100 Subject: Basis of header line I think the icon on the button is too small, but that's not currently configurable. Will have to look into that. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 41 ++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 4ad376e490..0dd196c26b 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -3,8 +3,45 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.3 -Label +import Cura 1.0 as Cura +import UM 1.0 as UM + +Item { - text: "Test!" + Column + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + RowLayout + { + spacing: UM.Theme.getSize("default_margin").width + + Cura.SecondaryButton + { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: UM.Theme.getSize("action_button").height + Layout.preferredWidth: height + + onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. + + tooltip: catalog.i18nc("@button:tooltip", "Back") + toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight + iconSource: UM.Theme.getIcon("ArrowLeft") + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: leftPadding + } + + Label + { + Layout.alignment: Qt.AlignVCenter + + text: "Install Plug-ins" //TODO: Depend on package type, and translate. + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + } + } + } } \ No newline at end of file -- cgit v1.2.3 From feb3046eacb924bcb5290cfad5d0716f113f9f5b Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:31:34 +0100 Subject: Add "readmore" button --- .../Marketplace/resources/qml/OnboardBanner.qml | 55 ++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8a29030514..150377eaf3 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -70,7 +70,8 @@ Rectangle } // Body - Text { + Label { + id: infoText anchors { top: parent.top @@ -80,8 +81,56 @@ Rectangle } font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("primary_text") - wrapMode: Text.WordWrap text: bannerBody + + renderType: Text.NativeRendering + color: "white" + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: + { + if(line.isLast) + { + // Check if read more button still fits after the body text + if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) + { + // If it does place it after the body text + readMoreButton.anchors.left = infoText.left; + readMoreButton.anchors.bottom = infoText.bottom; + readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); + readMoreButton.anchors.leftMargin = 0; + } + else + { + // Otherwise place it under the text + readMoreButton.anchors.left = infoText.left; + readMoreButton.anchors.bottom = infoText.bottom; + readMoreButton.anchors.leftMargin = line.implicitWidth + UM.Theme.getSize("default_margin").width; + readMoreButton.anchors.bottomMargin = 0; + } + } + } + } + + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } + + Cura.TertiaryButton + { + id: readMoreButton + text: "Learn More" + textFont: UM.Theme.getFont("default") + textColor: infoText.color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + height: fontMetrics.height + + onClicked: print("TODO") } } \ No newline at end of file -- cgit v1.2.3 From 964de6c7fbe3cc229a5608de110d3c691f7f04d0 Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:31:47 +0100 Subject: Change font size in banner --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 150377eaf3..cbd151bed2 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -80,7 +80,7 @@ Rectangle margins: UM.Theme.getSize("default_margin").width } - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") text: bannerBody renderType: Text.NativeRendering -- cgit v1.2.3 From f1ef1c283ea7283c269ec3c13af47420d7fcd4e6 Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:48:38 +0100 Subject: Add links to read me support pages Note that since the support pages are not yet ready the "read more" buttons are hidden. Once the correct link is added they become visible again. --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Marketplace.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 +++- plugins/Marketplace/resources/qml/Packages.qml | 1 + plugins/Marketplace/resources/qml/Plugins.qml | 1 + 6 files changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index a329d992e5..e8e54d77b5 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -14,6 +14,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() bannerIcon: "ArrowDoubleCircleRight" bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlaceManagePackagesBanner(); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 14de458e95..2bdd0a509d 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -54,6 +54,7 @@ Window bannerBody: content.item && content.item.bannerBody bannerIcon: content.item && content.item.bannerIcon onRemoveBanner: content.item && content.item.onRemoveBanner + readMoreUrl: content.item.readMoreUrl && content.item.readMoreUrl } // Page title. diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 32d2c2213a..fc4869dac5 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,6 +10,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() bannerIcon: "Spool" bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlaceMaterialBanner(); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index cbd151bed2..4b5e494be5 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -15,6 +15,7 @@ Rectangle property string bannerIcon property string bannerBody property var onRemoveBanner + property string readMoreUrl visible: bannerVisible @@ -121,6 +122,7 @@ Rectangle Cura.TertiaryButton { + visible: readMoreUrl !== "" id: readMoreButton text: "Learn More" textFont: UM.Theme.getFont("default") @@ -131,6 +133,6 @@ Rectangle isIconOnRightSide: true height: fontMetrics.height - onClicked: print("TODO") + onClicked: Qt.openUrlExternally(readMoreUrl) } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 91f6448994..9ef35a31b3 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -14,6 +14,7 @@ ListView property bool bannerVisible property string bannerIcon property string bannerBody + property string readMoreUrl property var onRemoveBanner width: parent.width diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index b9d38c6e2b..f0c101153c 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,6 +10,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() bannerIcon: "Shop" bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlacePluginBanner(); bannerVisible = false; -- cgit v1.2.3 From e5c8d5456de7e288905ccb92d7673ac6f13590ff Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:50:52 +0100 Subject: Directly use bindings in banner pages CURA-8564 --- cura/CuraApplication.py | 27 ---------------------- .../Marketplace/resources/qml/ManagedPackages.qml | 4 ++-- plugins/Marketplace/resources/qml/Materials.qml | 5 ++-- plugins/Marketplace/resources/qml/Plugins.qml | 5 ++-- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6cf2593bbf..21924a2680 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -2015,33 +2015,6 @@ class CuraApplication(QtApplication): show_whatsnew_only = has_active_machine and has_app_just_upgraded return show_whatsnew_only - @pyqtSlot(result = bool) - def shouldShowMarketPlacePluginBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_plugin_banner") - - @pyqtSlot(result = bool) - def shouldShowMarketPlaceMaterialBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_material_banner") - - @pyqtSlot(result = bool) - def shouldShowMarketPlaceManagePackagesBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_manage_packages_banner") - - @pyqtSlot() - def closeMarketPlacePluginBanner(self) -> None: - Logger.log("i", "Close market place plugin banner") - self._preferences.setValue("cura/market_place_show_plugin_banner", False) - - @pyqtSlot() - def closeMarketPlaceMaterialBanner(self) -> None: - Logger.log("i", "Close market place material banner") - self._preferences.setValue("cura/market_place_show_material_banner", False) - - @pyqtSlot() - def closeMarketPlaceManagePackagesBanner(self) -> None: - Logger.log("i", "Close market place manage packages banner") - self._preferences.setValue("cura/market_place_show_manage_packages_banner", False) - @pyqtSlot(result = int) def appWidth(self) -> int: main_window = QtApplication.getInstance().getMainWindow() diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index e8e54d77b5..677a9ee574 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -11,12 +11,12 @@ Packages { pageTitle: catalog.i18nc("@header", "Manage packages") - bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: "ArrowDoubleCircleRight" bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlaceManagePackagesBanner(); + UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index fc4869dac5..e4cd554334 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -2,17 +2,18 @@ // Cura is released under the terms of the LGPLv3 or higher. import Marketplace 1.0 as Marketplace +import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Install Materials") - bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: "Spool" bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlaceMaterialBanner(); + UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index f0c101153c..11aabedb85 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -2,17 +2,18 @@ // Cura is released under the terms of the LGPLv3 or higher. import Marketplace 1.0 as Marketplace +import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") - bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: "Shop" bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlacePluginBanner(); + UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } -- cgit v1.2.3 From 00135e574dc464f1562c277ea389511dbc6735fb Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:53:35 +0100 Subject: Rename property `bannerBody` to `bannerText` CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- plugins/Marketplace/resources/qml/Packages.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 677a9ee574..0d1472bb6a 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -13,7 +13,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: "ArrowDoubleCircleRight" - bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 2bdd0a509d..fe1c967a42 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -51,7 +51,7 @@ Window OnboardBanner { bannerVisible: content.item && content.item.bannerVisible - bannerBody: content.item && content.item.bannerBody + bannerText: content.item && content.item.bannerText bannerIcon: content.item && content.item.bannerIcon onRemoveBanner: content.item && content.item.onRemoveBanner readMoreUrl: content.item.readMoreUrl && content.item.readMoreUrl diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index e4cd554334..a1d6d91f8c 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: "Spool" - bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 4b5e494be5..8d68512878 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -13,7 +13,7 @@ Rectangle { property bool bannerVisible property string bannerIcon - property string bannerBody + property string bannerText property var onRemoveBanner property string readMoreUrl @@ -82,7 +82,7 @@ Rectangle } font: UM.Theme.getFont("default") - text: bannerBody + text: bannerText renderType: Text.NativeRendering color: "white" diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 9ef35a31b3..e98818299c 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -13,7 +13,7 @@ ListView property string pageTitle property bool bannerVisible property string bannerIcon - property string bannerBody + property string bannerText property string readMoreUrl property var onRemoveBanner diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 11aabedb85..884529c3e0 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: "Shop" - bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) -- cgit v1.2.3 From f8d9f1e39c98546a31705d4b6fe489427f750810 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:55:33 +0100 Subject: Use `visible` property of `Rectangle` rather than defining a separate property CURA-8564 --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index fe1c967a42..1ae82adfff 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -50,7 +50,7 @@ Window OnboardBanner { - bannerVisible: content.item && content.item.bannerVisible + visible: content.item && content.item.bannerVisible bannerText: content.item && content.item.bannerText bannerIcon: content.item && content.item.bannerIcon onRemoveBanner: content.item && content.item.onRemoveBanner diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8d68512878..4a65297816 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,14 +11,11 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property bool bannerVisible property string bannerIcon property string bannerText property var onRemoveBanner property string readMoreUrl - visible: bannerVisible - Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors { -- cgit v1.2.3 From 0337c1c77605baaa3b7fb4e9b0897e31c47f2ea7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 11:57:12 +0100 Subject: Allow changing the icon size But use the same default as what was previously hard-coded. Now we can have buttons with non-standard icon sizes then, e.g. if the button size itself is also non-standard. Contributes to issue CURA-8565. --- resources/qml/ActionButton.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 62bea5df3b..f129e8c976 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -15,6 +15,7 @@ Button property bool isIconOnRightSide: false property alias iconSource: buttonIconLeft.source + property real iconSize: UM.Theme.getSize("action_button_icon").height property alias textFont: buttonText.font property alias cornerRadius: backgroundRect.radius property alias tooltip: tooltip.tooltipText @@ -158,7 +159,7 @@ Button { id: buttonIconRight source: buttonIconLeft.source - height: visible ? UM.Theme.getSize("action_button_icon").height : 0 + height: visible ? button.iconSize : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height -- cgit v1.2.3 From 6d1ffbde6920fd9dd08be8823f67a6f78fd91b24 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 11:59:46 +0100 Subject: Remove superfluous anchors.fill This is already set by the StackView, which (logically) requires that the children fill the entire space taken by the StackView. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index b293d21f92..951de77f19 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -53,7 +53,6 @@ Window ColumnLayout { id: packageBrowse - anchors.fill: parent spacing: UM.Theme.getSize("default_margin").height -- cgit v1.2.3 From f1a9bbd7914b3dbdce17e0e2ec4d02b779c6acf1 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:01:42 +0100 Subject: Use the `Item` element instead of `Rectangle` for banner icon To prevent un-necessary draw checks as the background is transparent CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 4a65297816..982bc0f501 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -28,7 +28,7 @@ Rectangle color: UM.Theme.getColor("action_panel_secondary") // Icon - Rectangle + Item { id: onboardingIcon anchors @@ -39,7 +39,6 @@ Rectangle } width: UM.Theme.getSize("button_icon").width height: UM.Theme.getSize("button_icon").height - color: "transparent" UM.RecolorImage { anchors.fill: parent -- cgit v1.2.3 From c117b61503d9091cf321997f9abfc9ace82b264b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:03:03 +0100 Subject: Also use proper icon size for left icon Almost forgot! Contributes to issue CURA-8565. --- resources/qml/ActionButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index f129e8c976..942c0ee578 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -110,7 +110,7 @@ Button { id: buttonIconLeft source: "" - height: visible ? UM.Theme.getSize("action_button_icon").height : 0 + height: visible ? button.iconSize : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height -- cgit v1.2.3 From 07ee729cdefe566f0efb0f7b1011545a9ef21127 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:03:29 +0100 Subject: Increase size of icon to fit button exactly Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 0dd196c26b..6cf29e9f45 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -29,9 +29,10 @@ Item tooltip: catalog.i18nc("@button:tooltip", "Back") toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight - iconSource: UM.Theme.getIcon("ArrowLeft") leftPadding: UM.Theme.getSize("narrow_margin").width rightPadding: leftPadding + iconSource: UM.Theme.getIcon("ArrowLeft") + iconSize: height - leftPadding * 2 } Label -- cgit v1.2.3 From 0ca20e9b3d0f6716cb3fff68442661375bc712e3 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:16:52 +0100 Subject: Use an `alias` property for the banner icon CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 10 ++-------- plugins/Marketplace/resources/qml/Packages.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 0d1472bb6a..9ce8408f8e 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -12,7 +12,7 @@ Packages pageTitle: catalog.i18nc("@header", "Manage packages") bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); - bannerIcon: "ArrowDoubleCircleRight" + bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight") bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index a1d6d91f8c..de075af031 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -9,7 +9,7 @@ Packages pageTitle: catalog.i18nc("@header", "Install Materials") bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") - bannerIcon: "Spool" + bannerIcon: UM.Theme.getIcon("Spool") bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 982bc0f501..b44295c90a 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,7 +11,7 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property string bannerIcon + property alias bannerIcon: onboardingIcon.source; property string bannerText property var onRemoveBanner property string readMoreUrl @@ -28,7 +28,7 @@ Rectangle color: UM.Theme.getColor("action_panel_secondary") // Icon - Item + UM.RecolorImage { id: onboardingIcon anchors @@ -39,12 +39,6 @@ Rectangle } width: UM.Theme.getSize("button_icon").width height: UM.Theme.getSize("button_icon").height - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon(bannerIcon) - } } // Close button diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index e98818299c..722ad0ccda 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -12,7 +12,7 @@ ListView property string pageTitle property bool bannerVisible - property string bannerIcon + property var bannerIcon property string bannerText property string readMoreUrl property var onRemoveBanner diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 884529c3e0..2922a39c9b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -9,7 +9,7 @@ Packages pageTitle: catalog.i18nc("@header", "Install Plugins") bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") - bannerIcon: "Shop" + bannerIcon: UM.Theme.getIcon("Shop") bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { -- cgit v1.2.3 From 6909085733a46bda1e99fe36c2eaba07ae6ed32d Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:26:48 +0100 Subject: Use color from theme for the market place banner banner text CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index b44295c90a..148a279cd2 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -75,7 +75,7 @@ Rectangle text: bannerText renderType: Text.NativeRendering - color: "white" + color: UM.Theme.getColor("primary_text") wrapMode: Text.Wrap elide: Text.ElideRight -- cgit v1.2.3 From b53c1ba3061b4fc3dbeb46e161f7f186fa399ceb Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:34:25 +0100 Subject: Move static anchor properties inside the `TertiaryButton` element CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 148a279cd2..62f6b96356 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -87,16 +87,12 @@ Rectangle if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) { // If it does place it after the body text - readMoreButton.anchors.left = infoText.left; - readMoreButton.anchors.bottom = infoText.bottom; readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); readMoreButton.anchors.leftMargin = 0; } else { // Otherwise place it under the text - readMoreButton.anchors.left = infoText.left; - readMoreButton.anchors.bottom = infoText.bottom; readMoreButton.anchors.leftMargin = line.implicitWidth + UM.Theme.getSize("default_margin").width; readMoreButton.anchors.bottomMargin = 0; } @@ -114,6 +110,8 @@ Rectangle { visible: readMoreUrl !== "" id: readMoreButton + anchors.left: infoText.left + anchors.bottom: infoText.bottom text: "Learn More" textFont: UM.Theme.getFont("default") textColor: infoText.color -- cgit v1.2.3 From 5dd2dcce906e87919722a170e0bff8453b27c289 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:57:36 +0100 Subject: Split page in header and contents This requires a small refactor here. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 79 ++++++++++++++-------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 6cf29e9f45..4b4b2123c7 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -10,39 +10,58 @@ import UM 1.0 as UM Item { - Column + RowLayout { - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width + id: header + anchors + { + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + } + + spacing: UM.Theme.getSize("default_margin").width + + Cura.SecondaryButton + { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: UM.Theme.getSize("action_button").height + Layout.preferredWidth: height + + onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. + + tooltip: catalog.i18nc("@button:tooltip", "Back") + toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: leftPadding + iconSource: UM.Theme.getIcon("ArrowLeft") + iconSize: height - leftPadding * 2 + } + + Label + { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true - RowLayout + text: "Install Plug-ins" //TODO: Depend on package type, and translate. + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + } + } + + Rectangle + { + anchors { - spacing: UM.Theme.getSize("default_margin").width - - Cura.SecondaryButton - { - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: UM.Theme.getSize("action_button").height - Layout.preferredWidth: height - - onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. - - tooltip: catalog.i18nc("@button:tooltip", "Back") - toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: leftPadding - iconSource: UM.Theme.getIcon("ArrowLeft") - iconSize: height - leftPadding * 2 - } - - Label - { - Layout.alignment: Qt.AlignVCenter - - text: "Install Plug-ins" //TODO: Depend on package type, and translate. - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - } + top: header.bottom + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + bottom: parent.bottom } + color: UM.Theme.getColor("detail_background") } } \ No newline at end of file -- cgit v1.2.3 From c136deb430b1a0f9a82195eea28e6fbc0bcf2c3e Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 14:34:15 +0100 Subject: Remove top anchor from an element used in ColumnLayout This element did need additional margins at the top. Added margins to the ColumnLayout element instead. CURA-8564 --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++-- plugins/Marketplace/resources/qml/OnboardBanner.qml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 1ae82adfff..e1936d6e44 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -40,6 +40,7 @@ Window Rectangle { anchors.fill: parent + anchors.topMargin: UM.Theme.getSize("default_margin").height color: UM.Theme.getColor("main_background") ColumnLayout @@ -61,7 +62,7 @@ Window Item { Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + Layout.preferredHeight: childrenRect.height Label { @@ -72,7 +73,6 @@ Window leftMargin: UM.Theme.getSize("default_margin").width right: parent.right rightMargin: UM.Theme.getSize("default_margin").width - bottom: parent.bottom } font: UM.Theme.getFont("large") diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 62f6b96356..0107d6d816 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -22,7 +22,6 @@ Rectangle margins: UM.Theme.getSize("default_margin").width left: parent.left right: parent.right - top: parent.top } color: UM.Theme.getColor("action_panel_secondary") -- cgit v1.2.3 From d5cfaa1e0a714bbff9fd3b535dc11618cd62c752 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 14:50:48 +0100 Subject: Add package card to detail page The card has the wrong layout, but it's a start. The data is communicated in any case. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 8 ++++++++ plugins/Marketplace/resources/qml/Packages.qml | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 4b4b2123c7..49e4d97633 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -10,6 +10,9 @@ import UM 1.0 as UM Item { + id: detailPage + property var packageData: packages.selectedPackage + RowLayout { id: header @@ -63,5 +66,10 @@ Item bottom: parent.bottom } color: UM.Theme.getColor("detail_background") + + PackageCard + { + packageData: detailPage.packageData + } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index def8d40acb..89254f2526 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -9,9 +9,10 @@ import UM 1.4 as UM ListView { id: packages + width: parent.width property string pageTitle - width: parent.width + property var selectedPackage clip: true @@ -69,7 +70,8 @@ ListView onClicked: { - contextStack.push(Qt.resolvedUrl("PackageDetails.qml")) + packages.selectedPackage = model.package; + contextStack.push(packageDetailsComponent); } PackageCard @@ -78,6 +80,16 @@ ListView } } + Component + { + id: packageDetailsComponent + + PackageDetails + { + packageData: packages.selectedPackage + } + } + //Wrapper item to add spacing between content and footer. footer: Item { -- cgit v1.2.3 From 6ca20ba863a71cc81e0f477d89a88ad04189d1db Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 15:09:33 +0100 Subject: Rename properties in marketplace onboarding banner CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Marketplace.qml | 8 ++++---- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 9 ++++----- plugins/Marketplace/resources/qml/Packages.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 9ce8408f8e..2610f7cd9d 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -14,7 +14,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight") bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index e1936d6e44..1866d7512d 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -52,10 +52,10 @@ Window OnboardBanner { visible: content.item && content.item.bannerVisible - bannerText: content.item && content.item.bannerText - bannerIcon: content.item && content.item.bannerIcon - onRemoveBanner: content.item && content.item.onRemoveBanner - readMoreUrl: content.item.readMoreUrl && content.item.readMoreUrl + text: content.item && content.item.bannerText + icon: content.item && content.item.bannerIcon + onRemove: content.item && content.item.onRemoveBanner + readMoreUrl: content.item && content.item.bannerReadMoreUrl } // Page title. diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index de075af031..2634f7b328 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -11,7 +11,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: UM.Theme.getIcon("Spool") bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 0107d6d816..0dbe2cb897 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,9 +11,9 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property alias bannerIcon: onboardingIcon.source; - property string bannerText - property var onRemoveBanner + property alias icon: onboardingIcon.source + property alias text: infoText.text + property var onRemove property string readMoreUrl Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height @@ -56,7 +56,7 @@ Rectangle hoverColor: UM.Theme.getColor("primary_text_hover") iconSource: UM.Theme.getIcon("Cancel") - onClicked: onRemoveBanner() + onClicked: onRemove() } // Body @@ -71,7 +71,6 @@ Rectangle } font: UM.Theme.getFont("default") - text: bannerText renderType: Text.NativeRendering color: UM.Theme.getColor("primary_text") diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 722ad0ccda..46ebe8a661 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -14,7 +14,7 @@ ListView property bool bannerVisible property var bannerIcon property string bannerText - property string readMoreUrl + property string bannerReadMoreUrl property var onRemoveBanner width: parent.width diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 2922a39c9b..29b264c702 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -11,7 +11,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: UM.Theme.getIcon("Shop") bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; -- cgit v1.2.3 From 616bf479e55c0352c35b7f9ab116e99235e28a1f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:11:15 +0100 Subject: Set position and width of card in details page This means that the card itself shouldn't specify a width. It should get a width from how it's used. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 1 - plugins/Marketplace/resources/qml/PackageDetails.qml | 9 +++++++++ plugins/Marketplace/resources/qml/Packages.qml | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index e80b352978..4cf3b84713 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,6 @@ Rectangle { property var packageData - width: parent ? parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width: 0 height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 49e4d97633..7db46300a8 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -70,6 +70,15 @@ Item PackageCard { packageData: detailPage.packageData + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + } } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 89254f2526..47318d5a72 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -77,6 +77,7 @@ ListView PackageCard { packageData: model.package + width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width } } -- cgit v1.2.3 From 61fdd5dc723e7390277ca7d8fe010ab8da192b00 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:32:55 +0100 Subject: Restore check for parent before getting its width Delegates that are outside of the viewport may have no parent any more. Don't attempt to find their parents' width. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Packages.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 47318d5a72..49fca00fdb 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -65,7 +65,7 @@ ListView delegate: MouseArea { - width: parent.width + width: parent ? parent.width : 0 height: childrenRect.height onClicked: -- cgit v1.2.3 From 8ecd2f86a4f055012ea8f84b4ef036c3d37fb897 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:38:24 +0100 Subject: Communicate to PackageCard whether it is a detailed card or not If it is detailed, it currently hides the short description. That is not quite enough, but we'll expand that behaviour. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 7 ++++--- plugins/Marketplace/resources/qml/PackageDetails.qml | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 4cf3b84713..9a9d59efe7 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -11,6 +11,7 @@ import Cura 1.6 as Cura Rectangle { property var packageData + property bool expanded: false height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") @@ -21,7 +22,7 @@ Rectangle State { name: "Folded" - when: true // TODO + when: !expanded PropertyChanges { target: descriptionArea @@ -30,8 +31,8 @@ Rectangle }, State { - name: "Header" - when: false // TODO + name: "Expanded" + when: expanded PropertyChanges { target: descriptionArea diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 7db46300a8..c1ef765a14 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -69,7 +69,6 @@ Item PackageCard { - packageData: detailPage.packageData anchors { left: parent.left @@ -79,6 +78,9 @@ Item top: parent.top topMargin: UM.Theme.getSize("default_margin").height } + + packageData: detailPage.packageData + expanded: true } } } \ No newline at end of file -- cgit v1.2.3 From d7058ef520206c073acfdb2c96706434abe95a08 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 17:40:53 +0100 Subject: Change card summary information into a column This is necessary because we can't anchor to the bottom of the card here, and because we want to swap out the description for a download count in the extended detail card. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 418 +++++++++++----------- 1 file changed, 207 insertions(+), 211 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 9a9d59efe7..6832d30074 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -57,271 +57,267 @@ Rectangle source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } - // Title row. - RowLayout + ColumnLayout { - id: titleBar anchors { left: packageItem.right + leftMargin: UM.Theme.getSize("default_margin").width right: parent.right + rightMargin: UM.Theme.getSize("thick_margin").width top: parent.top topMargin: UM.Theme.getSize("narrow_margin").height - leftMargin: UM.Theme.getSize("default_margin").width - rightMargin:UM.Theme.getSize("thick_margin").width } + height: packageItem.height + packageItem.anchors.margins * 2 - Label + // Title row. + RowLayout { - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignTop - } + id: titleBar + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Label + { + text: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } + Control + { + 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: + 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 / 2)) + } + + Rectangle { - switch(packageData.packageType) + anchors.fill: parent + color: UM.Theme.getColor("action_button_hovered") + radius: width + UM.RecolorImage { - 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"); + anchors.fill: parent + color: UM.Theme.getColor("primary") + source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") } } - visible: parent.hovered - targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) + + //NOTE: Can we link to something here? (Probably a static link explaining what verified is): + // onClicked: Qt.openUrlExternally( XXXXXX ) } - Rectangle + Control { - anchors.fill: parent - color: UM.Theme.getColor("action_button_hovered") - radius: width + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip + { + tooltipText: "" // TODO + visible: parent.hovered + } + UM.RecolorImage { anchors.fill: parent + color: UM.Theme.getColor("primary") - source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") + source: UM.Theme.getIcon("CheckCircle") // TODO } - } - //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 - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO } - UM.RecolorImage + Label { - anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO + id: packageVersionLabel + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true } - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } - - Label - { - id: packageVersionLabel - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - Layout.fillWidth: true - } - - Button - { - id: externalLinkButton + Button + { + id: externalLinkButton - // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: UM.Theme.getSize("narrow_margin").width - topPadding: UM.Theme.getSize("narrow_margin").width - bottomPadding: UM.Theme.getSize("narrow_margin").width + // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: UM.Theme.getSize("narrow_margin").width + topPadding: UM.Theme.getSize("narrow_margin").width + bottomPadding: UM.Theme.getSize("narrow_margin").width - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - contentItem: UM.RecolorImage - { - source: UM.Theme.getIcon("LinkExternal") - color: UM.Theme.getColor("icon") - implicitWidth: UM.Theme.getSize("card_tiny_icon").width - implicitHeight: UM.Theme.getSize("card_tiny_icon").height - } + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + contentItem: UM.RecolorImage + { + source: UM.Theme.getIcon("LinkExternal") + color: UM.Theme.getColor("icon") + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + } - background: Rectangle - { - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" - radius: externalLinkButton.width / 2 + background: Rectangle + { + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" + radius: externalLinkButton.width / 2 + } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } - } - - Item - { - id: descriptionArea - height: descriptionLabel.height - anchors - { - top: titleBar.bottom - left: packageItem.right - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - leftMargin: UM.Theme.getSize("default_margin").width - } - Label + Item { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - - text: packageData.description - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight - - onLineLaidOut: + id: descriptionArea + Layout.preferredWidth: parent.width + Layout.fillHeight: true + + Label { - if(truncated && line.isLast) + id: descriptionLabel + width: parent.width + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + + text: packageData.description + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else + if(truncated && line.isLast) { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) + { + line.width = max_line_width; + } + else + { + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. + } + descriptionLabel.lastLineWidth = line.implicitWidth; } - descriptionLabel.lastLineWidth = line.implicitWidth; } } + Label + { + id: tripleDotLabel + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: readMoreButton.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated + } + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: parent.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } } - Label - { - id: tripleDotLabel - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: readMoreButton.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated - } - Cura.TertiaryButton - { - id: readMoreButton - anchors.left: tripleDotLabel.right - anchors.bottom: parent.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } - } - // Author and action buttons. - RowLayout - { - id: authorAndActionButton - width: parent.width - anchors + // Author and action buttons. + RowLayout { - bottom: parent.bottom - left: packageItem.right - margins: UM.Theme.getSize("default_margin").height - } - spacing: UM.Theme.getSize("narrow_margin").width + id: authorAndActionButton + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height - Label - { - id: authorBy - Layout.alignment: Qt.AlignTop + spacing: UM.Theme.getSize("narrow_margin").width - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } + Label + { + id: authorBy + Layout.alignment: Qt.AlignTop - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } - Cura.SecondaryButton - { - id: disableButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required - } + Cura.TertiaryButton + { + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop + + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } - Cura.SecondaryButton - { - id: uninstallButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required - } + Cura.SecondaryButton + { + id: disableButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Disable") + visible: false // not functional right now, also only when unfolding and required + } - Cura.PrimaryButton - { - id: installButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required + Cura.SecondaryButton + { + id: uninstallButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Uninstall") + visible: false // not functional right now, also only when unfolding and required + } + + Cura.PrimaryButton + { + id: installButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Update") // OR Download, if new! + visible: false // not functional right now, also only when unfolding and required + } } } -- cgit v1.2.3 From 443ba67455964fa52b2528a56717f82b3b31e289 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 17:49:11 +0100 Subject: Align ellipsis to text again It used to be fine, but the font of the button got made different from the font of the description. It should be the same as the description, so this should remain correct even if the fonts change again. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 6832d30074..77fd98f031 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -231,7 +231,7 @@ Rectangle id: tripleDotLabel anchors.left: parent.left anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: readMoreButton.bottom + anchors.bottom: descriptionLabel.bottom text: "… " font: descriptionLabel.font -- cgit v1.2.3 From 0dcc28032ae6b87edaf924940d911e4b23e2e3ff Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:03:42 +0100 Subject: Add download count design to detail card Just the design. The data is just a placeholder so far. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 42 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 77fd98f031..ef22ff04b8 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -25,9 +25,14 @@ Rectangle when: !expanded PropertyChanges { - target: descriptionArea + target: shortDescription visible: true } + PropertyChanges + { + target: downloadCount + visible: false + } }, State { @@ -35,9 +40,14 @@ Rectangle when: expanded PropertyChanges { - target: descriptionArea + target: shortDescription visible: false } + PropertyChanges + { + target: downloadCount + visible: true + } } ] @@ -192,7 +202,7 @@ Rectangle Item { - id: descriptionArea + id: shortDescription Layout.preferredWidth: parent.width Layout.fillHeight: true @@ -259,6 +269,32 @@ Rectangle } } + Row + { + id: downloadCount + Layout.preferredWidth: parent.width + Layout.fillHeight: true + + UM.RecolorImage + { + id: downloadsIcon + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height + + source: UM.Theme.getIcon("Download") + color: UM.Theme.getColor("text") + } + + Label + { + anchors.verticalCenter: downloadsIcon.verticalCenter + + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("default") + text: "123456789" + } + } + // Author and action buttons. RowLayout { -- cgit v1.2.3 From cfd29b268d8a7f1d06b0e92ef6138f9be7dc05ee Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:05:53 +0100 Subject: Use actual download count for packages Quite easy. It turned out the model already had this information, due to foresight when the card itself got implemented. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index ef22ff04b8..59bd90de9b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -291,7 +291,7 @@ Rectangle color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") - text: "123456789" + text: packageData.downloadCount } } -- cgit v1.2.3 From 0546f58e575a61977ab3a449956161a765ba2acd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:26:28 +0100 Subject: Add extended description header Wrapping the whole content so far in another column so that we can have a wider part below, automatically aligned. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 540 ++++++++++++---------- 1 file changed, 288 insertions(+), 252 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 59bd90de9b..1a9e32b03b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -13,7 +13,7 @@ Rectangle property var packageData property bool expanded: false - height: UM.Theme.getSize("card").height + height: childrenRect.height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width @@ -33,6 +33,11 @@ Rectangle target: downloadCount visible: false } + PropertyChanges + { + target: extendedDescription + visible: false + } }, State { @@ -48,312 +53,343 @@ Rectangle target: downloadCount visible: true } + PropertyChanges + { + target: extendedDescription + visible: true + } } ] - // Separate column for icon on the left. - Image + Column { - id: packageItem - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - width: UM.Theme.getSize("card_icon").width - height: width - - source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" - } + width: parent.width - ColumnLayout - { - anchors - { - left: packageItem.right - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("thick_margin").width - top: parent.top - topMargin: UM.Theme.getSize("narrow_margin").height - } - height: packageItem.height + packageItem.anchors.margins * 2 + spacing: 0 - // Title row. - RowLayout + Item { - id: titleBar - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + width: parent.width + height: UM.Theme.getSize("card").height - Label + Image { - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignTop + id: packageItem + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("card_icon").width + height: width + + source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } - Control + ColumnLayout { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + anchors + { + left: packageItem.right + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("thick_margin").width + top: parent.top + topMargin: UM.Theme.getSize("narrow_margin").height + } + height: packageItem.height + packageItem.anchors.margins * 2 + // Title row. + RowLayout + { + id: titleBar + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height - enabled: packageData.isCheckedByUltimaker - visible: packageData.isCheckedByUltimaker + Label + { + text: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } - Cura.ToolTip - { - tooltipText: + Control + { + 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 / 2)) + } + + 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 { - switch(packageData.packageType) + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip { - 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"); + tooltipText: "" // TODO + visible: parent.hovered } + + UM.RecolorImage + { + anchors.fill: parent + + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") // TODO + } + + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO } - visible: parent.hovered - targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) - } - Rectangle - { - anchors.fill: parent - color: UM.Theme.getColor("action_button_hovered") - radius: width - UM.RecolorImage + Label { - anchors.fill: parent - color: UM.Theme.getColor("primary") - source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") + id: packageVersionLabel + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true } - } - //NOTE: Can we link to something here? (Probably a static link explaining what verified is): - // onClicked: Qt.openUrlExternally( XXXXXX ) - } + Button + { + id: externalLinkButton - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered + // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: UM.Theme.getSize("narrow_margin").width + topPadding: UM.Theme.getSize("narrow_margin").width + bottomPadding: UM.Theme.getSize("narrow_margin").width + + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + contentItem: UM.RecolorImage + { + source: UM.Theme.getIcon("LinkExternal") + color: UM.Theme.getColor("icon") + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + } + + background: Rectangle + { + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" + radius: externalLinkButton.width / 2 + } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } } - UM.RecolorImage + Item { - anchors.fill: parent + id: shortDescription + Layout.preferredWidth: parent.width + Layout.fillHeight: true - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO + Label + { + id: descriptionLabel + width: parent.width + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + + text: packageData.description + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: + { + if(truncated && line.isLast) + { + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) + { + line.width = max_line_width; + } + else + { + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. + } + descriptionLabel.lastLineWidth = line.implicitWidth; + } + } + } + Label + { + id: tripleDotLabel + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: descriptionLabel.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated + } + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: parent.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } } - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } + Row + { + id: downloadCount + Layout.preferredWidth: parent.width + Layout.fillHeight: true - Label - { - id: packageVersionLabel - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - Layout.fillWidth: true - } + UM.RecolorImage + { + id: downloadsIcon + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height - Button - { - id: externalLinkButton + source: UM.Theme.getIcon("Download") + color: UM.Theme.getColor("text") + } - // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: UM.Theme.getSize("narrow_margin").width - topPadding: UM.Theme.getSize("narrow_margin").width - bottomPadding: UM.Theme.getSize("narrow_margin").width + Label + { + anchors.verticalCenter: downloadsIcon.verticalCenter - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - contentItem: UM.RecolorImage - { - source: UM.Theme.getIcon("LinkExternal") - color: UM.Theme.getColor("icon") - implicitWidth: UM.Theme.getSize("card_tiny_icon").width - implicitHeight: UM.Theme.getSize("card_tiny_icon").height + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("default") + text: packageData.downloadCount + } } - background: Rectangle + // Author and action buttons. + RowLayout { - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" - radius: externalLinkButton.width / 2 - } - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - } + id: authorAndActionButton + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height - Item - { - id: shortDescription - Layout.preferredWidth: parent.width - Layout.fillHeight: true + spacing: UM.Theme.getSize("narrow_margin").width - Label - { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + Label + { + id: authorBy + Layout.alignment: Qt.AlignTop - text: packageData.description - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } - onLineLaidOut: - { - if(truncated && line.isLast) + Cura.TertiaryButton { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else - { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. - } - descriptionLabel.lastLineWidth = line.implicitWidth; + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignTop + + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } - } - } - Label - { - id: tripleDotLabel - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: descriptionLabel.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated - } - Cura.TertiaryButton - { - id: readMoreButton - anchors.right: parent.right - anchors.bottom: parent.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } - } - Row - { - id: downloadCount - Layout.preferredWidth: parent.width - Layout.fillHeight: true - - UM.RecolorImage - { - id: downloadsIcon - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - - source: UM.Theme.getIcon("Download") - color: UM.Theme.getColor("text") - } + Cura.SecondaryButton + { + id: disableButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Disable") + visible: false // not functional right now, also only when unfolding and required + } - Label - { - anchors.verticalCenter: downloadsIcon.verticalCenter + Cura.SecondaryButton + { + id: uninstallButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Uninstall") + visible: false // not functional right now, also only when unfolding and required + } - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default") - text: packageData.downloadCount + Cura.PrimaryButton + { + id: installButton + Layout.alignment: Qt.AlignTop + text: catalog.i18nc("@button", "Update") // OR Download, if new! + visible: false // not functional right now, also only when unfolding and required + } + } } } - // Author and action buttons. - RowLayout + Column { - id: authorAndActionButton - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - spacing: UM.Theme.getSize("narrow_margin").width + id: extendedDescription + padding: UM.Theme.getSize("default_margin").width + topPadding: 0 Label { - id: authorBy - Layout.alignment: Qt.AlignTop - - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") + text: catalog.i18nc("@header", "Description") + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") } - - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - - Cura.SecondaryButton - { - id: disableButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required - } - - Cura.SecondaryButton - { - id: uninstallButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required - } - - Cura.PrimaryButton - { - id: installButton - Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required - } } } -- cgit v1.2.3 From e173fa2d9d6fd119859d1498d5ad00dbd3e97418 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:33:58 +0100 Subject: Add package description in full to detail card I realise that this might cause the card to become taller than the window. Might need to do something about that. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 1a9e32b03b..e435699717 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -381,14 +381,30 @@ Rectangle Column { id: extendedDescription + width: parent.width + padding: UM.Theme.getSize("default_margin").width topPadding: 0 + spacing: UM.Theme.getSize("default_margin").height Label { + width: parent.width - parent.padding * 2 + text: catalog.i18nc("@header", "Description") - font: UM.Theme.getFont("medium_bold") + font: UM.Theme.getFont("default_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width - parent.padding * 2 + + text: packageData.description + font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") + wrapMode: Text.Wrap } } } -- cgit v1.2.3 From 09b4bd2ac235252c4a75017f728a2df9b73159ae Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:37:54 +0100 Subject: Add forgotten ArrowLeft icon I've been using this for a while. It should've been included with the commit that added the button, but oh well. Contributes to issue CURA-8565. --- resources/themes/cura-light/icons/default/ArrowLeft.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/ArrowLeft.svg diff --git a/resources/themes/cura-light/icons/default/ArrowLeft.svg b/resources/themes/cura-light/icons/default/ArrowLeft.svg new file mode 100644 index 0000000000..d722b8ae8d --- /dev/null +++ b/resources/themes/cura-light/icons/default/ArrowLeft.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From ffd1a4d8125422ac3dcb83f70fd79726195c9675 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:46:56 +0100 Subject: Add button to visit plug-in website There are a lot of buttons leading to websites now: An arrow leading to the author website. An author name leading to the author website. A 'read more' label leading to the plug-in website and this new button leading to the plug-in website. Maybe we should raise this with the designer. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 10 ++++++++++ resources/themes/cura-light/icons/default/Globe.svg | 3 +++ 2 files changed, 13 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/Globe.svg diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index e435699717..ada49c5f53 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -406,6 +406,16 @@ Rectangle color: UM.Theme.getColor("text") wrapMode: Text.Wrap } + + Cura.SecondaryButton + { + anchors.horizontalCenter: parent.horizontalCenter + + text: catalog.i18nc("@button", "Visit plug-in website") + iconSource: UM.Theme.getIcon("Globe") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } } } diff --git a/resources/themes/cura-light/icons/default/Globe.svg b/resources/themes/cura-light/icons/default/Globe.svg new file mode 100644 index 0000000000..4d955e9615 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Globe.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file -- cgit v1.2.3 From d511c4542ab3748d7bb45db8ef491c13b388eefb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:04:30 +0100 Subject: Make package detail page scroll if details are too long Some plug-ins could have very long descriptions now. We show all of it, but that could go off the screen in theory. This makes the content scrollable if it goes off the screen. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index c1ef765a14..1ccb3dc4fd 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -67,20 +67,30 @@ Item } color: UM.Theme.getColor("detail_background") - PackageCard + ScrollView { - anchors + anchors.fill: parent + + clip: true //Need to clip, not for the bottom (which is off the window) but for the top (which would overlap the header). + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + contentHeight: expandedPackageCard.height + UM.Theme.getSize("default_margin").height * 2 + + PackageCard { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: anchors.leftMargin - top: parent.top - topMargin: UM.Theme.getSize("default_margin").height - } + id: expandedPackageCard + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + } - packageData: detailPage.packageData - expanded: true + packageData: detailPage.packageData + expanded: true + } } } } \ No newline at end of file -- cgit v1.2.3 From b5c7dfe9a261d7f7bc76bc29621e63bb34bfacba Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:29:33 +0100 Subject: Format links in descriptions to be clickable Took some fiddling to get the regex right. But it's nice now. Contributes to issue CURA-8565. --- plugins/Marketplace/PackageModel.py | 18 ++++++++++++++---- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 93d73bbc83..294b34c8b3 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -2,10 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, QObject +import re from typing import Any, Dict, Optional -from UM.Util import parseBool - from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. catalog = i18nCatalog("cura") @@ -35,9 +34,9 @@ class PackageModel(QObject): self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. self._download_count = package_data.get("download_count", 0) - self._description = package_data.get("description", "") + self._description = self._format(package_data.get("description", "")) - self._download_url = package_data.get("download_url", "") # Not used yet, will be. + self._download_url = package_data.get("download_url", "") self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? author_data = package_data.get("author", {}) @@ -49,6 +48,17 @@ class PackageModel(QObject): self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. + def _format(self, text): + """ + Formats a user-readable block of text for display. + :return: A block of rich text with formatting embedded. + """ + # Turn all in-line hyperlinks into actual links. + url_regex = re.compile(r"(((http|https)://)[a-zA-Z0-9@:%._+~#?&/=]{2,256}\.[a-z]{2,12}(/[a-zA-Z0-9@:%.-_+~#?&/=]*)?)") + text = re.sub(url_regex, r'\1', text) + + return text + @pyqtProperty(str, constant = True) def packageId(self) -> str: return self._package_id diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index ada49c5f53..2c0aaa250a 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -404,7 +404,11 @@ Rectangle text: packageData.description font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") + linkColor: UM.Theme.getColor("text_link") wrapMode: Text.Wrap + textFormat: Text.RichText + + onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) } Cura.SecondaryButton -- cgit v1.2.3 From d96284ee3eafcb5d9e26656bb29cb25bcb484ffc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:35:57 +0100 Subject: Only show formatted description in detail page We can't show rich text in the package list, because the we use the onLineLaidOut signal there, which doesn't work with Rich Text. So for the package list we should NOT use the formatted version of the description because that will contain ugly HTML tags that the user wouldn't want to see. Show the original description there. Use the formatted description only in the detail page where we don't use onLineLaidOut. Contributes to issue CURA-8565. --- plugins/Marketplace/PackageModel.py | 9 +++++++-- plugins/Marketplace/resources/qml/PackageCard.qml | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 294b34c8b3..7528a71440 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -34,7 +34,8 @@ class PackageModel(QObject): self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. self._download_count = package_data.get("download_count", 0) - self._description = self._format(package_data.get("description", "")) + self._description = package_data.get("description", "") + self._formatted_description = self._format(self._description) self._download_url = package_data.get("download_url", "") self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? @@ -48,7 +49,7 @@ class PackageModel(QObject): self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. - def _format(self, text): + def _format(self, text: str) -> str: """ Formats a user-readable block of text for display. :return: A block of rich text with formatting embedded. @@ -95,6 +96,10 @@ class PackageModel(QObject): def description(self): return self._description + @pyqtProperty(str, constant = True) + def formattedDescription(self) -> str: + return self._formatted_description + @pyqtProperty(str, constant=True) def authorName(self): return self._author_name diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 2c0aaa250a..8f94dc990b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -233,6 +233,7 @@ Rectangle property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description + textFormat: Text.PlainText //Must be plain text, or we won't get onLineLaidOut signals. Don't auto-detect! font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") maximumLineCount: 2 @@ -401,7 +402,7 @@ Rectangle { width: parent.width - parent.padding * 2 - text: packageData.description + text: packageData.formattedDescription font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") linkColor: UM.Theme.getColor("text_link") -- cgit v1.2.3 From d0eee2cffe4f75229fc77080c701ec41d60a6dbd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:41:25 +0100 Subject: Make header of detail page depend on header of origin It's the same as the list of packages you came from, now. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 3 ++- plugins/Marketplace/resources/qml/Packages.qml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 1ccb3dc4fd..fdf1c8f92c 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -12,6 +12,7 @@ Item { id: detailPage property var packageData: packages.selectedPackage + property string title: catalog.i18nc("@header", "Package details") RowLayout { @@ -49,7 +50,7 @@ Item Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true - text: "Install Plug-ins" //TODO: Depend on package type, and translate. + text: detailPage.title font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") } diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 49fca00fdb..75207bc2e6 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -19,8 +19,6 @@ ListView Component.onCompleted: model.updatePackages() Component.onDestruction: model.abortUpdating() - //ScrollBar.vertical.policy: ScrollBar.AlwaysOff - spacing: UM.Theme.getSize("default_margin").height section.property: "package.sectionTitle" @@ -88,6 +86,7 @@ ListView PackageDetails { packageData: packages.selectedPackage + title: packages.pageTitle } } -- cgit v1.2.3 From f385e3d6394d3fefa0c382a5af115140b4dd340f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:59:43 +0100 Subject: Don't show elision or read more if there is no description at all Apparently Qt marks it as 'truncated' then, even though it's not really. Don't show the ... nor the 'read more' button if there is nothing more to read. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 8f94dc990b..9f0dda920c 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -239,6 +239,7 @@ Rectangle maximumLineCount: 2 wrapMode: Text.Wrap elide: Text.ElideRight + visible: text !== "" onLineLaidOut: { @@ -267,7 +268,7 @@ Rectangle text: "… " font: descriptionLabel.font color: descriptionLabel.color - visible: descriptionLabel.truncated + visible: descriptionLabel.truncated && descriptionLabel.text !== "" } Cura.TertiaryButton { @@ -279,7 +280,7 @@ Rectangle text: catalog.i18nc("@info", "Read more") iconSource: UM.Theme.getIcon("LinkExternal") - visible: descriptionLabel.truncated + visible: descriptionLabel.truncated && descriptionLabel.text !== "" enabled: visible leftPadding: UM.Theme.getSize("default_margin").width rightPadding: UM.Theme.getSize("wide_margin").width -- cgit v1.2.3 From cac623b509127640e2bcaceb2532c68782edc295 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 10:49:49 +0100 Subject: Use medium font size for extended description The design appears more balanced then, quoth the designer of the layout. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 9f0dda920c..bb43f926b6 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -394,7 +394,7 @@ Rectangle width: parent.width - parent.padding * 2 text: catalog.i18nc("@header", "Description") - font: UM.Theme.getFont("default_bold") + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") elide: Text.ElideRight } @@ -404,7 +404,7 @@ Rectangle width: parent.width - parent.padding * 2 text: packageData.formattedDescription - font: UM.Theme.getFont("default") + font: UM.Theme.getFont("medium") color: UM.Theme.getColor("text") linkColor: UM.Theme.getColor("text_link") wrapMode: Text.Wrap -- cgit v1.2.3 From d96ba0dcf35f1270d4dfadc7112799090b85fd35 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 10:51:43 +0100 Subject: Easy navigation to Cloud marketplace CURA-8563 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Marketplace.qml | 11 +++++++++++ plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/Packages.qml | 1 + plugins/Marketplace/resources/qml/Plugins.qml | 1 + 5 files changed, 15 insertions(+) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 2610f7cd9d..b90bffd723 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -19,6 +19,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" model: Marketplace.LocalPackageList { diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 1866d7512d..4fd8c9e999 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -162,6 +162,17 @@ Window } } + Cura.TertiaryButton + { + text: catalog.i18nc("@info", "Search in the browser") + iconSource: UM.Theme.getIcon("LinkExternal") + + isIconOnRightSide: true + font: UM.Theme.getFont("default") + + onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl) + } + // Page contents. Rectangle { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 2634f7b328..3afe7b412a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -16,6 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials" model: Marketplace.RemotePackageList { diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 46ebe8a661..c192cc5dd9 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -11,6 +11,7 @@ ListView id: packages property string pageTitle + property string searchInBrowserUrl property bool bannerVisible property var bannerIcon property string bannerText diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 29b264c702..c473a3a48e 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -16,6 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" model: Marketplace.RemotePackageList { -- cgit v1.2.3 From 49db5be3aa731f8c9be6fe39f77ccbb8ee26f622 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 10:52:15 +0100 Subject: Decrease margin sizes They were way bigger compared to the UX design CURA-8563 --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 4fd8c9e999..a825e712f7 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -47,7 +47,7 @@ Window { anchors.fill: parent - spacing: UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("thin_margin").height OnboardBanner { @@ -89,7 +89,7 @@ Window RowLayout { width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("thin_margin").height spacing: UM.Theme.getSize("thin_margin").width Rectangle -- cgit v1.2.3 From 82d148d0773ad2ec11041a2b0139f74b1193685c Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:08:09 +0100 Subject: Add correct text to material and plugins onboarding banners CURA-8564 --- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 2634f7b328..cbab3a354e 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: UM.Theme.getIcon("Spool") - bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 29b264c702..635c0a8d22 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: UM.Theme.getIcon("Shop") - bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) -- cgit v1.2.3 From ca602067284f48a4b84157d7dc9f2c803f5bd109 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:10:55 +0100 Subject: Always show read more button in on boarding banner Even if there is no link CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 0dbe2cb897..90af5f9b4f 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -106,7 +106,6 @@ Rectangle Cura.TertiaryButton { - visible: readMoreUrl !== "" id: readMoreButton anchors.left: infoText.left anchors.bottom: infoText.bottom -- cgit v1.2.3 From 8e6210fb2cb87425221baba7a53d24154a6dbba0 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:11:32 +0100 Subject: Change margins of read more button in marketplace onboarding banner To comply with UX design CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 90af5f9b4f..a2c1613bcb 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -85,8 +85,8 @@ Rectangle if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) { // If it does place it after the body text - readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); - readMoreButton.anchors.leftMargin = 0; + readMoreButton.anchors.bottomMargin = -(fontMetrics.height); + readMoreButton.anchors.leftMargin = UM.Theme.getSize("thin_margin").width; } else { -- cgit v1.2.3 From 5a148e459f7c5a8284ad592d8079bd5b191fd124 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:19:32 +0100 Subject: Decrease size of the icons in the marketplace onboarding banners CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- resources/themes/cura-light/theme.json | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index a2c1613bcb..f77f8bee97 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -36,8 +36,8 @@ Rectangle left: parent.left margins: UM.Theme.getSize("default_margin").width } - width: UM.Theme.getSize("button_icon").width - height: UM.Theme.getSize("button_icon").height + width: UM.Theme.getSize("banner_icon_size").width + height: UM.Theme.getSize("banner_icon_size").height } // Close button diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 81885faaf0..c29ddc2a86 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -683,6 +683,8 @@ "table_row": [2.0, 2.0], "welcome_wizard_content_image_big": [18, 15], - "welcome_wizard_cloud_content_image": [4, 4] + "welcome_wizard_cloud_content_image": [4, 4], + + "banner_icon_size": [2.0, 2.0] } } -- cgit v1.2.3 From d291ea85a28ff5bea76c7a0fea4f78655d34cbdb Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:20:21 +0100 Subject: Place the on boarding banners in the correct place in the marketplace To comply with the UX design CURA-8564 --- plugins/Marketplace/resources/qml/Marketplace.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 1866d7512d..53a1f189bc 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -49,15 +49,6 @@ Window spacing: UM.Theme.getSize("default_margin").height - OnboardBanner - { - visible: content.item && content.item.bannerVisible - text: content.item && content.item.bannerText - icon: content.item && content.item.bannerIcon - onRemove: content.item && content.item.onRemoveBanner - readMoreUrl: content.item && content.item.bannerReadMoreUrl - } - // Page title. Item { @@ -81,6 +72,15 @@ Window } } + OnboardBanner + { + visible: content.item && content.item.bannerVisible + text: content.item && content.item.bannerText + icon: content.item && content.item.bannerIcon + onRemove: content.item && content.item.onRemoveBanner + readMoreUrl: content.item && content.item.bannerReadMoreUrl + } + // Search & Top-Level Tabs Item { -- cgit v1.2.3 From c62b21ad44b5c9adb4619f949a7876d226d74957 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:57:22 +0100 Subject: Revert "Decrease margin sizes" This reverts commit 49db5be3aa731f8c9be6fe39f77ccbb8ee26f622. --- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index a825e712f7..4fd8c9e999 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -47,7 +47,7 @@ Window { anchors.fill: parent - spacing: UM.Theme.getSize("thin_margin").height + spacing: UM.Theme.getSize("default_margin").height OnboardBanner { @@ -89,7 +89,7 @@ Window RowLayout { width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("thin_margin").height + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("thin_margin").width Rectangle -- cgit v1.2.3 From 97ba4489ffc47360be7c51a0ac96c5b3915be1ed Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 13:01:45 +0100 Subject: Remove unneeded margin from search bar Cura 8563 --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 4fd8c9e999..7b975e1c2f 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -89,7 +89,7 @@ Window RowLayout { width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + height: UM.Theme.getSize("button_icon").height spacing: UM.Theme.getSize("thin_margin").width Rectangle -- cgit v1.2.3 From 7eca00565927234fc793d221acdb2b624c984e9f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 13:24:12 +0100 Subject: Increase size of icons on action buttons We want those to be 1.5em now. This has an effect on all action buttons with icons in the interface! Contributes to issue CURA-8565. --- resources/themes/cura-light/theme.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 81885faaf0..c56a983049 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -563,7 +563,7 @@ "button_lining": [0, 0], "action_button": [15.0, 2.5], - "action_button_icon": [1.0, 1.0], + "action_button_icon": [1.5, 1.5], "action_button_radius": [0.15, 0.15], "dialog_primary_button_padding": [3.0, 0], -- cgit v1.2.3 From b0275cfba9e9150adc6af952f4c40a0a39774d5c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Nov 2021 14:17:40 +0100 Subject: Add campaign links CURA-8563 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index b90bffd723..f44fbd0a9b 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -19,7 +19,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" model: Marketplace.LocalPackageList { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 3afe7b412a..489915aa10 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -16,7 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-materials-browser" model: Marketplace.RemotePackageList { diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index c473a3a48e..3b0b5d7c23 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -16,7 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" model: Marketplace.RemotePackageList { -- cgit v1.2.3 From 7529483cb0242694a5496a60b5bf74d233adf477 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:42:12 +0100 Subject: Don't show download count for bundled plug-ins I considered rewriting the section title property to be QML-only and translate it from the QML, but this is a bit simpler in the end, even though there is data duplication now. Contributes to issue CURA-8565. --- plugins/Marketplace/LocalPackageList.py | 2 +- plugins/Marketplace/PackageModel.py | 8 +++++++- plugins/Marketplace/RemotePackageList.py | 3 ++- plugins/Marketplace/resources/qml/PackageCard.qml | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 6acbaa8500..7e9bd82cdb 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -90,4 +90,4 @@ class LocalPackageList(PackageList): bundled_or_installed = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" package_type = package_info["package_type"] section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] - return PackageModel(package_info, section_title = section_title, parent = self) + return PackageModel(package_info, installation_status = bundled_or_installed, section_title = section_title, parent = self) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 7528a71440..c4389434e4 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -17,10 +17,11 @@ class PackageModel(QObject): QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: + def __init__(self, package_data: Dict[str, Any], installation_status: str, section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: """ Constructs a new model for a single package. :param package_data: The data received from the Marketplace API about the package to create. + :param installation_status: Whether the package is `not_installed`, `installed` or `bundled`. :param section_title: If the packages are to be categorized per section provide the section_title :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ @@ -46,6 +47,7 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") + self._installation_status = installation_status self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. @@ -108,6 +110,10 @@ class PackageModel(QObject): def authorInfoUrl(self): return self._author_info_url + @pyqtProperty(str, constant = True) + def installationStatus(self) -> str: + return self._installation_status + @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index e7df498fbf..6241ce0d2c 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -134,8 +134,9 @@ class RemotePackageList(PackageList): return for package_data in response_data["data"]: + installation_status = "installed" if CuraApplication.getInstance().getPackageManager().isUserInstalledPackage(package_data["package_id"]) else "not_installed" try: - package = PackageModel(package_data, parent = self) + package = PackageModel(package_data, installation_status, parent = self) 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/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index bb43f926b6..1c4066f64e 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -303,6 +303,7 @@ Rectangle width: UM.Theme.getSize("card_tiny_icon").width height: UM.Theme.getSize("card_tiny_icon").height + visible: packageData.installationStatus !== "bundled" //Don't show download count for packages that are bundled. It'll usually be 0. source: UM.Theme.getIcon("Download") color: UM.Theme.getColor("text") } @@ -311,6 +312,7 @@ Rectangle { anchors.verticalCenter: downloadsIcon.verticalCenter + visible: packageData.installationStatus !== "bundled" //Don't show download count for packages that are bundled. It'll usually be 0. color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") text: packageData.downloadCount -- cgit v1.2.3 From c48c449354276c61f7dadc93cf79767e51d39e76 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:50:45 +0100 Subject: Show hover colour when hovering a card This signals to the user they can select one. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Packages.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 75207bc2e6..94f594c6cf 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -63,9 +63,11 @@ ListView delegate: MouseArea { + id: cardMouseArea width: parent ? parent.width : 0 height: childrenRect.height + hoverEnabled: true onClicked: { packages.selectedPackage = model.package; @@ -76,6 +78,7 @@ ListView { packageData: model.package width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width + color: cardMouseArea.containsMouse ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("main_background") } } -- cgit v1.2.3 From c1f0fb1faf5b6caba4b6d989220bc159fc0c84af Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:56:09 +0100 Subject: Make icons smaller for tertiary buttons These are typically visually smaller buttons, since they don't have an outline. It makes more sense to use the size of the text then, or something thereabouts. Contributes to issue CURA-8565. --- resources/qml/TertiaryButton.qml | 1 + resources/themes/cura-light/theme.json | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/qml/TertiaryButton.qml b/resources/qml/TertiaryButton.qml index 76684b6ef2..8171188232 100644 --- a/resources/qml/TertiaryButton.qml +++ b/resources/qml/TertiaryButton.qml @@ -16,4 +16,5 @@ Cura.ActionButton textDisabledColor: UM.Theme.getColor("action_button_disabled_text") hoverColor: "transparent" underlineTextOnHover: true + iconSize: UM.Theme.getSize("action_button_icon_small").height } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index c56a983049..a6cec6de8b 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -564,6 +564,7 @@ "action_button": [15.0, 2.5], "action_button_icon": [1.5, 1.5], + "action_button_icon_small": [1.0, 1.0], "action_button_radius": [0.15, 0.15], "dialog_primary_button_padding": [3.0, 0], -- cgit v1.2.3 From 02cf4ac440ed9f31213a8071170fed07f70f97ec Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 15:01:39 +0100 Subject: Fix newline rendering in extended display Rich Text is rendered a bit like HTML, where all of the whitespace gets changed into a single space. This is normally not so bad, but with newlines it's annoying. This preserves the newlines from the description. Contributes to issue CURA-8565. --- plugins/Marketplace/PackageModel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index c4389434e4..a0d68969f1 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -60,6 +60,9 @@ class PackageModel(QObject): url_regex = re.compile(r"(((http|https)://)[a-zA-Z0-9@:%._+~#?&/=]{2,256}\.[a-z]{2,12}(/[a-zA-Z0-9@:%.-_+~#?&/=]*)?)") text = re.sub(url_regex, r'\1', text) + # Turn newlines into
so that they get displayed as newlines when rendering as rich text. + text = text.replace("\n", "
") + return text @pyqtProperty(str, constant = True) -- cgit v1.2.3 From cd09af885d1ab25fd688b8698575a2f92450cf32 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 15:57:44 +0100 Subject: Fix resetting when Marketplace is closed and re-opened Previously, this would cause the Marketplace to freeze. We're still not entirely sure why. It seems to be a bug in Qt, but it's rather hard to deal with. This new solution is nicer in some ways but not as neat in others. - We're no longer clearing the content of the loader, so the QML and the package data remains in memory while the Marketplace is closed. We deem this to not be a problem, because the memory usage of this package data is only a couple of kB, nothing compared to the memory used by the slicer when it loads a model. - On the other hand, it's now possible to programmatically change the tab there, instead of manually having to click the buttons. - Fixes a bug where the highlighted tab of of the tab bar doesn't update when closing and re-opening the Marketplace. And a bug where there was a search bar for the manage page while it didn't work. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 951de77f19..a02c0e0d5f 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -21,8 +21,14 @@ Window width: minimumWidth height: minimumHeight - // Set and unset the content. No need to keep things in memory if it's not visible. - onVisibleChanged: content.source = visible ? "Plugins.qml" : "" + onVisibleChanged: + { + pageSelectionTabBar.currentIndex = 0; //Go back to the initial tab. + while(contextStack.depth > 1) + { + contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? + } + } Connections { @@ -116,33 +122,33 @@ Window spacing: 0 background: Rectangle { color: "transparent" } + onCurrentIndexChanged: + { + searchBar.text = ""; + searchBar.visible = currentItem.hasSearch; + content.source = currentItem.sourcePage; + } + PackageTypeTab { id: pluginTabText width: implicitWidth text: catalog.i18nc("@button", "Plugins") - onClicked: - { - searchBar.text = "" - searchBar.visible = true - content.source = "Plugins.qml" - } + property string sourcePage: "Plugins.qml" + property bool hasSearch: true } PackageTypeTab { id: materialsTabText width: implicitWidth text: catalog.i18nc("@button", "Materials") - onClicked: - { - searchBar.text = "" - searchBar.visible = true - content.source = "Materials.qml" - } + property string sourcePage: "Materials.qml" + property bool hasSearch: true } ManagePackagesButton { - onClicked: content.source = "ManagedPackages.qml" + property string sourcePage: "ManagedPackages.qml" + property bool hasSearch: false } } -- cgit v1.2.3 From b4020614d5deb0ef72883a598ee964e162236285 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 17:17:50 +0100 Subject: Add material links to package model They will not be initialised for plug-ins. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index a0d68969f1..8049a1aed3 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -41,6 +41,11 @@ class PackageModel(QObject): self._download_url = package_data.get("download_url", "") self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? + subdata = package_data.get("data", {}) + self._technical_data_sheet = self._findLink(subdata, "technical_data_sheet") + self._safety_data_sheet = self._findLink(subdata, "safety_data_sheet") + self._where_to_buy = self._findLink(subdata, "where_to_buy") + author_data = package_data.get("author", {}) self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) self._author_info_url = author_data.get("website", "") @@ -51,6 +56,22 @@ class PackageModel(QObject): self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. + def _findLink(self, subdata: Dict[str, Any], link_type: str) -> str: + """ + Searches the package data for a link of a certain type. + + The links are not in a fixed path in the package data. We need to iterate over the available links to find them. + :param subdata: The "data" element in the package data, which should contain links. + :param link_type: The type of link to find. + :return: A URL of where the link leads, or an empty string if there is no link of that type in the package data. + """ + links = subdata.get("links", []) + for link in links: + if link.get("type", "") == link_type: + return link.get("url", "") + else: + return "" # No link with the correct type was found. + def _format(self, text: str) -> str: """ Formats a user-readable block of text for display. @@ -120,3 +141,15 @@ class PackageModel(QObject): @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title + + @pyqtProperty(str, constant = True) + def technicalDataSheet(self) -> str: + return self._technical_data_sheet + + @pyqtProperty(str, constant = True) + def safetyDataSheet(self) -> str: + return self._safety_data_sheet + + @pyqtProperty(str, constant = True) + def whereToBuy(self) -> str: + return self._where_to_buy -- cgit v1.2.3 From 4ffca8da986fa36de68d16b2a2ccd08d68c3ba0d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 17:29:28 +0100 Subject: Implement getting the compatible printers of a package Again, only really applicable to materials for now. But it's simple to keep this class generic. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 8049a1aed3..578b73a82e 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, QObject import re -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. catalog = i18nCatalog("cura") @@ -45,6 +45,7 @@ class PackageModel(QObject): self._technical_data_sheet = self._findLink(subdata, "technical_data_sheet") self._safety_data_sheet = self._findLink(subdata, "safety_data_sheet") self._where_to_buy = self._findLink(subdata, "where_to_buy") + self._compatible_printers = self._getCompatiblePrinters(subdata) author_data = package_data.get("author", {}) self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) @@ -86,6 +87,28 @@ class PackageModel(QObject): return text + def _getCompatiblePrinters(self, subdata: Dict[str, Any]) -> List[str]: + """ + Gets the list of printers that this package provides material compatibility with. + + Any printer is listed, even if it's only for a single nozzle on a single material in the package. + :param subdata: The "data" element in the package data, which should contain this compatibility information. + :return: A list of printer names that this package provides material compatibility with. + """ + result = set() + + for material in subdata.get("materials", []): + for compatibility in material.get("compatibility", []): + printer_name = compatibility.get("machine_name") + if printer_name is None: + continue # Missing printer name information. Skip this one. + for subcompatibility in compatibility.get("compatibilities", []): + if subcompatibility.get("hardware_compatible", False): + result.add(printer_name) + break + + return list(sorted(result)) + @pyqtProperty(str, constant = True) def packageId(self) -> str: return self._package_id @@ -153,3 +176,7 @@ class PackageModel(QObject): @pyqtProperty(str, constant = True) def whereToBuy(self) -> str: return self._where_to_buy + + @pyqtProperty("QVariantList", constant = True) + def compatiblePrinters(self) -> List[str]: + return self._compatible_printers -- cgit v1.2.3 From 02c1e017887f32d90b456748e34ecadddfe8f6e0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 17:42:29 +0100 Subject: Parse list of compatible support materials This one is quite complex because the support material names are in their profiles, so we need to consult the profiles, if present. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 578b73a82e..605674ca16 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -5,7 +5,9 @@ from PyQt5.QtCore import pyqtProperty, QObject import re from typing import Any, Dict, List, Optional +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. + catalog = i18nCatalog("cura") @@ -46,6 +48,7 @@ class PackageModel(QObject): self._safety_data_sheet = self._findLink(subdata, "safety_data_sheet") self._where_to_buy = self._findLink(subdata, "where_to_buy") self._compatible_printers = self._getCompatiblePrinters(subdata) + self._compatible_support_materials = self._getCompatibleSupportMaterials(subdata) author_data = package_data.get("author", {}) self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) @@ -109,6 +112,35 @@ class PackageModel(QObject): return list(sorted(result)) + def _getCompatibleSupportMaterials(self, subdata: Dict[str, Any]) -> List[str]: + """ + Gets the list of support materials that the materials in this package are compatible with. + + Since the materials are individually encoded as keys in the API response, only PVA and Breakaway are currently + supported. + :param subdata: The "data" element in the package data, which should contain this compatibility information. + :return: A list of support materials that the materials in this package are compatible with. + """ + result = set() + + container_registry = CuraContainerRegistry.getInstance() + try: + pva_name = container_registry.findContainersMetadata(id = "ultimaker_pva")[0].get("name", "Ultimaker PVA") + except IndexError: + pva_name = "Ultimaker PVA" + try: + breakaway_name = container_registry.findContainersMetadata(id = "ultimaker_bam")[0].get("name", "Ultimaker Breakaway") + except IndexError: + breakaway_name = "Ultimaker Breakaway" + + for material in subdata.get("materials", []): + if material.get("pva_compatible", False): + result.add(pva_name) + if material.get("breakaway_compatible", False): + result.add(breakaway_name) + + return list(sorted(result)) + @pyqtProperty(str, constant = True) def packageId(self) -> str: return self._package_id @@ -180,3 +212,7 @@ class PackageModel(QObject): @pyqtProperty("QVariantList", constant = True) def compatiblePrinters(self) -> List[str]: return self._compatible_printers + + @pyqtProperty("QVariantList", constant = True) + def compatibleSupportMaterials(self) -> List[str]: + return self._compatible_support_materials -- cgit v1.2.3 From 1418bb072c4474a81ded376eaba46b159799c917 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 17:50:39 +0100 Subject: Parse compatibility information of air manager and material station The union vs. intersection here is a guess. I'm guessing the online Marketplace displays compatibility if ANY materials/combinations are compatible, not requiring that ALL materials/combinations are compatible. We'll have to review that. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 605674ca16..5f7b66b935 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -49,6 +49,8 @@ class PackageModel(QObject): self._where_to_buy = self._findLink(subdata, "where_to_buy") self._compatible_printers = self._getCompatiblePrinters(subdata) self._compatible_support_materials = self._getCompatibleSupportMaterials(subdata) + self._is_compatible_material_station = self._isCompatibleMaterialStation(subdata) + self._is_compatible_air_manager = self._isCompatibleAirManager(subdata) author_data = package_data.get("author", {}) self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author")) @@ -141,6 +143,30 @@ class PackageModel(QObject): return list(sorted(result)) + def _isCompatibleMaterialStation(self, subdata: Dict[str, Any]) -> bool: + """ + Finds out if this package provides any material that is compatible with the material station. + :param subdata: The "data" element in the package data, which should contain this compatibility information. + :return: Whether this package provides any material that is compatible with the material station. + """ + for material in subdata.get("materials", []): + for compatibility in material.get("compatibilities", []): + if compatibility.get("material_station_optimized", False): + return True + return False + + def _isCompatibleAirManager(self, subdata: Dict[str, Any]) -> bool: + """ + Finds out if this package provides any material that is compatible with the air manager. + :param subdata: The "data" element in the package data, which should contain this compatibility information. + :return: Whether this package provides any material that is compatible with the air manager. + """ + for material in subdata.get("materials", []): + for compatibility in material.get("compatibilities", []): + if compatibility.get("air_manager_optimized", False): + return True + return False + @pyqtProperty(str, constant = True) def packageId(self) -> str: return self._package_id @@ -216,3 +242,11 @@ class PackageModel(QObject): @pyqtProperty("QVariantList", constant = True) def compatibleSupportMaterials(self) -> List[str]: return self._compatible_support_materials + + @pyqtProperty(bool, constant = True) + def isCompatibleMaterialStation(self) -> bool: + return self._is_compatible_material_station + + @pyqtProperty(bool, constant = True) + def isCompatibleAirManager(self) -> bool: + return self._is_compatible_air_manager -- cgit v1.2.3 From 9c51d620b2abd4fa3de93924c0fcf75297403204 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:07:13 +0100 Subject: Use QStringList rather than QVariantList We know that they are strings after all. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 5f7b66b935..752016e12f 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -235,11 +235,11 @@ class PackageModel(QObject): def whereToBuy(self) -> str: return self._where_to_buy - @pyqtProperty("QVariantList", constant = True) + @pyqtProperty("QStringList", constant = True) def compatiblePrinters(self) -> List[str]: return self._compatible_printers - @pyqtProperty("QVariantList", constant = True) + @pyqtProperty("QStringList", constant = True) def compatibleSupportMaterials(self) -> List[str]: return self._compatible_support_materials -- cgit v1.2.3 From fd026e472f665ea58189055c3ae8b65a16b224bc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:08:42 +0100 Subject: Show compatible printers for materials Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 1c4066f64e..25ba92886e 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -415,6 +415,40 @@ Rectangle onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) } + Column //Separate column to have no spacing between compatible printers. + { + id: compatiblePrinterColumn + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible printers") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Repeater + { + model: packageData.compatiblePrinters + + Label + { + width: compatiblePrinterColumn.width + + text: modelData + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + } + Cura.SecondaryButton { anchors.horizontalCenter: parent.horizontalCenter -- cgit v1.2.3 From cfdb01caaf088ca3a48b07e8f6bb78c78f58fec3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:13:06 +0100 Subject: Show list of compatible support materials Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 25ba92886e..98059d9938 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -449,6 +449,40 @@ Rectangle } } + Column + { + id: compatibleSupportMaterialColumn + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible support materials") + font: UM.Theme.getFont("medium_bold") + color: UM.THeme.getColor("text") + elide: Text.ElideRight + } + + Repeater + { + model: packageData.compatibleSupportMaterials + + Label + { + width: compatibleSupportMaterialColumn.width + + text: modelData + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + } + Cura.SecondaryButton { anchors.horizontalCenter: parent.horizontalCenter -- cgit v1.2.3 From b854025daa017013211d5287694886e2b800c555 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:22:34 +0100 Subject: Fix name of compatibility category I had the nesting wrong. 'compatibilities' is the nested sub-dict in each of these compatibility entries. Contributes to issue CURA-8585. --- plugins/Marketplace/PackageModel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 752016e12f..859c6c46f0 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -150,7 +150,7 @@ class PackageModel(QObject): :return: Whether this package provides any material that is compatible with the material station. """ for material in subdata.get("materials", []): - for compatibility in material.get("compatibilities", []): + for compatibility in material.get("compatibility", []): if compatibility.get("material_station_optimized", False): return True return False @@ -162,7 +162,7 @@ class PackageModel(QObject): :return: Whether this package provides any material that is compatible with the air manager. """ for material in subdata.get("materials", []): - for compatibility in material.get("compatibilities", []): + for compatibility in material.get("compatibility", []): if compatibility.get("air_manager_optimized", False): return True return False -- cgit v1.2.3 From aba3e755f4e0abd9c3e95e27415781e9307ae06c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:22:55 +0100 Subject: Show compatibility with material station Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 98059d9938..36c7338232 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -463,7 +463,7 @@ Rectangle text: catalog.i18nc("@header", "Compatible support materials") font: UM.Theme.getFont("medium_bold") - color: UM.THeme.getColor("text") + color: UM.Theme.getColor("text") elide: Text.ElideRight } @@ -483,6 +483,34 @@ Rectangle } } + Column + { + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible with material station") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width + + text: packageData.isCompatibleMaterialStation ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + Cura.SecondaryButton { anchors.horizontalCenter: parent.horizontalCenter -- cgit v1.2.3 From 39f540ff529770a681de5512978b74e40e9af88b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 18:25:24 +0100 Subject: Add information on compatibilities with air manager Similar to the Material Station. Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 36c7338232..e978f41396 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -494,7 +494,7 @@ Rectangle { width: parent.width - text: catalog.i18nc("@header", "Compatible with material station") + text: catalog.i18nc("@header", "Compatible with Material Station") font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") elide: Text.ElideRight @@ -511,6 +511,34 @@ Rectangle } } + Column + { + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Optimized for Air Manager") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width + + text: packageData.isCompatibleAirManager ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + Cura.SecondaryButton { anchors.horizontalCenter: parent.horizontalCenter -- cgit v1.2.3 From 58cefcb68c39aaa69f4599934b04f7171c22af20 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 19:00:08 +0100 Subject: Add additional buttons at the bottom for materials with links to data sheets And where to buy it. Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 41 +++++++++++++++++++--- .../cura-light/icons/default/DocumentFilled.svg | 3 ++ .../cura-light/icons/default/ShoppingCart.svg | 3 ++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 resources/themes/cura-light/icons/default/DocumentFilled.svg create mode 100644 resources/themes/cura-light/icons/default/ShoppingCart.svg diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index e978f41396..be20b92ddd 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -539,14 +539,45 @@ Rectangle } } - Cura.SecondaryButton + Row { + id: externalButtonRow anchors.horizontalCenter: parent.horizontalCenter - text: catalog.i18nc("@button", "Visit plug-in website") - iconSource: UM.Theme.getIcon("Globe") - outlineColor: "transparent" - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + Cura.SecondaryButton + { + text: packageData.packageType === "plugin" ? catalog.i18nc("@button", "Visit plug-in website") : catalog.i18nc("@button", "Website") + iconSource: UM.Theme.getIcon("Globe") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Buy spool") + iconSource: UM.Theme.getIcon("ShoppingCart") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.whereToBuy) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Safety datasheet") + iconSource: UM.Theme.getIcon("Warning") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.safetyDataSheet) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Technical datasheet") + iconSource: UM.Theme.getIcon("DocumentFilled") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.technicalDataSheet) + } } } } diff --git a/resources/themes/cura-light/icons/default/DocumentFilled.svg b/resources/themes/cura-light/icons/default/DocumentFilled.svg new file mode 100644 index 0000000000..bb654fea33 --- /dev/null +++ b/resources/themes/cura-light/icons/default/DocumentFilled.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/themes/cura-light/icons/default/ShoppingCart.svg b/resources/themes/cura-light/icons/default/ShoppingCart.svg new file mode 100644 index 0000000000..b3fece3fab --- /dev/null +++ b/resources/themes/cura-light/icons/default/ShoppingCart.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From 249a07269e9b58efc0fb0704310815b028d93956 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 19:02:27 +0100 Subject: Specify spacing between external link buttons Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index be20b92ddd..9d21551a83 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -544,6 +544,8 @@ Rectangle id: externalButtonRow anchors.horizontalCenter: parent.horizontalCenter + spacing: UM.Theme.getSize("narrow_margin").width + Cura.SecondaryButton { text: packageData.packageType === "plugin" ? catalog.i18nc("@button", "Visit plug-in website") : catalog.i18nc("@button", "Website") -- cgit v1.2.3 From d8e212581b90d4687e6f796fccfda721f1a97958 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 19:11:02 +0100 Subject: Show placeholder texts when compatibility lists are empty When there are no compatible printers, we show that there is no information. After all, all materials should be compatible with some printer. When there are no compatible support materials, we simply show 'None', because a material could be incompatible with all known support material types. Contributes to issue CURA-8585. --- plugins/Marketplace/resources/qml/PackageCard.qml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 9d21551a83..54c1a7efb7 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -447,6 +447,17 @@ Rectangle elide: Text.ElideRight } } + + Label + { + width: parent.width + + visible: packageData.compatiblePrinters.length == 0 + text: "(" + catalog.i18nc("@info", "No compatibility information") + ")" + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } } Column @@ -481,6 +492,17 @@ Rectangle elide: Text.ElideRight } } + + Label + { + width: parent.width + + visible: packageData.compatibleSupportMaterials.length == 0 + text: "(" + catalog.i18nc("@info No materials", "None") + ")" + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } } Column -- cgit v1.2.3 From bf168388dd49e61797e8fd2528279accd1dab909 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 1 Dec 2021 10:29:21 +0100 Subject: Change styling of "Search in the browser" button in the marketplace To comply with the UX design. cura 8563 --- plugins/Marketplace/resources/qml/Marketplace.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 39a7557de6..471c7523cb 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -168,7 +168,10 @@ Window iconSource: UM.Theme.getIcon("LinkExternal") isIconOnRightSide: true - font: UM.Theme.getFont("default") + height: UM.theme.getSize("standard_list_lineheight").height + + textFont: UM.Theme.getFont("default") + textColor: UM.Theme.getColor("text") onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl) } -- cgit v1.2.3 From bd9722654cd579532aae45e00924f8346b72e402 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 1 Dec 2021 11:45:30 +0100 Subject: Use FontMetrics component to calculate the line height cura 8563 --- plugins/Marketplace/resources/qml/Marketplace.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 471c7523cb..1773066bbd 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -162,15 +162,20 @@ Window } } + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } + Cura.TertiaryButton { text: catalog.i18nc("@info", "Search in the browser") iconSource: UM.Theme.getIcon("LinkExternal") isIconOnRightSide: true - height: UM.theme.getSize("standard_list_lineheight").height - - textFont: UM.Theme.getFont("default") + height: fontMetrics.height + textFont: fontMetrics.font textColor: UM.Theme.getColor("text") onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl) -- cgit v1.2.3 From 1a787e5df28ea7c238702adca9f150d7350399cf Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 1 Dec 2021 13:22:50 +0100 Subject: Solve layout warnings. --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 24e73e9f87..84e5b61dcf 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -126,7 +126,7 @@ Window TabBar { id: pageSelectionTabBar - anchors.right: parent.right + Layout.alignment: Qt.AlignRight height: UM.Theme.getSize("button_icon").height spacing: 0 background: Rectangle { color: "transparent" } diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index f77f8bee97..25e4b53241 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -17,12 +17,8 @@ Rectangle property string readMoreUrl Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height - anchors - { - margins: UM.Theme.getSize("default_margin").width - left: parent.left - right: parent.right - } + Layout.fillWidth: true + Layout.margins: UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("action_panel_secondary") -- cgit v1.2.3 From b5e06f6c670fca615d4dd2bec37be63bea929582 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 1 Dec 2021 13:48:18 +0100 Subject: Fix tooltip targetpoint. --- plugins/Marketplace/resources/qml/PackageCard.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 54c1a7efb7..b8f815bedf 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -120,7 +120,6 @@ Rectangle Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - enabled: packageData.isCheckedByUltimaker visible: packageData.isCheckedByUltimaker @@ -136,7 +135,7 @@ Rectangle } } visible: parent.hovered - targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 2)) + targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 4)) } Rectangle -- cgit v1.2.3 From c1298c6a5eb1fd93dcbe429e05a1f5a450956ae2 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 1 Dec 2021 13:53:43 +0100 Subject: Hide search link when not needed. --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 84e5b61dcf..236d6a2a24 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -180,7 +180,7 @@ Window { text: catalog.i18nc("@info", "Search in the browser") iconSource: UM.Theme.getIcon("LinkExternal") - + visible: pageSelectionTabBar.currentItem.hasSearch isIconOnRightSide: true font: UM.Theme.getFont("default") -- cgit v1.2.3 From 518e59303dedfea9b8abac85ab8a4e766a7323ac Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 1 Dec 2021 14:01:41 +0100 Subject: Fix dashes in links. part of CURA-8565 and/or CURA-8585 --- plugins/Marketplace/PackageModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 859c6c46f0..9b8c873827 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -84,7 +84,7 @@ class PackageModel(QObject): :return: A block of rich text with formatting embedded. """ # Turn all in-line hyperlinks into actual links. - url_regex = re.compile(r"(((http|https)://)[a-zA-Z0-9@:%._+~#?&/=]{2,256}\.[a-z]{2,12}(/[a-zA-Z0-9@:%.-_+~#?&/=]*)?)") + url_regex = re.compile(r"(((http|https)://)[a-zA-Z0-9@:%.\-_+~#?&/=]{2,256}\.[a-z]{2,12}(/[a-zA-Z0-9@:%.\-_+~#?&/=]*)?)") text = re.sub(url_regex, r'\1', text) # Turn newlines into
so that they get displayed as newlines when rendering as rich text. -- cgit v1.2.3 From 5373e9a36d45bc6ede3eee27df8b246434cd2ec6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 29 Nov 2021 12:32:00 +0100 Subject: More generic way of showing the manage buttons Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 40 +++++++++++++++++++++-- plugins/Marketplace/resources/qml/PackageCard.qml | 16 ++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 93d73bbc83..24f9671b34 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -1,11 +1,10 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtProperty, QObject +from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from typing import Any, Dict, Optional -from UM.Util import parseBool - +from UM.Logger import Logger from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. catalog = i18nCatalog("cura") @@ -28,6 +27,9 @@ class PackageModel(QObject): super().__init__(parent) self._package_id = package_data.get("package_id", "UnknownPackageId") self._package_type = package_data.get("package_type", "") + self._is_installed = package_data.get("is_installed", False) + self._is_active = package_data.get("is_active", False) + self._is_bundled = package_data.get("is_bundled", False) self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) tags = package_data.get("tags", []) @@ -96,3 +98,35 @@ class PackageModel(QObject): @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title + + enableManageButtonChanged = pyqtSignal() + + @pyqtProperty(str, notify = enableManageButtonChanged) + def enableManageButtonText(self): + if self._is_active: + return catalog.i18nc("@button", "Disable") + else: + return catalog.i18nc("@button", "Enable") + + @pyqtProperty(bool, notify = enableManageButtonChanged) + def enableManageButtonVisible(self): + return self._is_installed + + installManageButtonChanged = pyqtSignal() + + @pyqtProperty(str, notify = installManageButtonChanged) + def installManageButtonText(self): + if self._is_installed: + return catalog.i18nc("@button", "Uninstall") + else: + return catalog.i18nc("@button", "Install") + + @pyqtProperty(bool, notify = installManageButtonChanged) + def installManageButtonVisible(self): + return not self._is_bundled + + updateManageButtonChanged = pyqtSignal() + + @pyqtProperty(bool, notify = updateManageButtonChanged) + def updateManageButtonVisible(self): + return False # Todo: implement diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 433b77a54d..d4f9d74246 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -302,26 +302,26 @@ Rectangle Cura.SecondaryButton { - id: disableButton + id: enableManageButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required + text: packageData.enableManageButtonText + visible: packageData.enableManageButtonVisible } Cura.SecondaryButton { - id: uninstallButton + id: installManageButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required + text: packageData.installManageButtonText + visible: packageData.installManageButtonVisible } Cura.PrimaryButton { - id: installButton + id: updateManageButton Layout.alignment: Qt.AlignTop text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required + visible: packageData.updateManageButtonVisible } } -- cgit v1.2.3 From 3c225f1a73269373ac2b78311acc9a318475593a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 29 Nov 2021 14:13:29 +0100 Subject: Fixed some layout issues with the Package Cards Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 8 +++++--- resources/themes/cura-light/theme.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index d4f9d74246..040436375d 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -185,10 +185,11 @@ Rectangle } + // Description area Item { id: descriptionArea - height: descriptionLabel.height + height: childrenRect.height > descriptionLabel.height ? childrenRect.height : descriptionLabel.height anchors { top: titleBar.bottom @@ -269,6 +270,7 @@ Rectangle { bottom: parent.bottom left: packageItem.right + right: parent.right margins: UM.Theme.getSize("default_margin").height } spacing: UM.Theme.getSize("narrow_margin").width @@ -276,7 +278,7 @@ Rectangle Label { id: authorBy - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignVCenter text: catalog.i18nc("@label", "By") font: UM.Theme.getFont("default") @@ -287,7 +289,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignVCenter text: packageData.authorName textFont: UM.Theme.getFont("default_bold") diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 81885faaf0..dede26db0b 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -554,7 +554,7 @@ "standard_list_lineheight": [1.5, 1.5], "standard_arrow": [1.0, 1.0], - "card": [25.0, 8.75], + "card": [25.0, 10], "card_icon": [6.0, 6.0], "card_tiny_icon": [1.5, 1.5], -- cgit v1.2.3 From a59307e10d8a82492354ec1356706fadc935c05d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Nov 2021 14:21:18 +0100 Subject: Also set correct size for package card title --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 040436375d..e2d7b3c0b7 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -66,7 +66,7 @@ Rectangle left: packageItem.right right: parent.right top: parent.top - topMargin: UM.Theme.getSize("default_margin").height + topMargin: UM.Theme.getSize("narrow_margin").height leftMargin: UM.Theme.getSize("default_margin").width rightMargin:UM.Theme.getSize("thick_margin").width } @@ -74,7 +74,7 @@ Rectangle Label { text: packageData.displayName - font: UM.Theme.getFont("large_bold") + font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") verticalAlignment: Text.AlignTop } -- cgit v1.2.3 From 34911380d2ed515c2bbc8bb8e5c4c123d37d7517 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Nov 2021 14:14:43 +0100 Subject: Change fonts to default No idea why they were medium, but the design clearly shows that it should be the default font --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index e2d7b3c0b7..f57facf0c0 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -205,7 +205,7 @@ Rectangle property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. text: packageData.description - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") maximumLineCount: 2 wrapMode: Text.Wrap @@ -330,6 +330,6 @@ Rectangle FontMetrics { id: fontMetrics - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") } } -- cgit v1.2.3 From 26a39f024013ff05ff0ba5d4b94634ca8e92e5c0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 26 Nov 2021 16:04:23 +0100 Subject: Add a StackView around Marketplace to allow extra pages on top This allows a sort of full-screen pop-up to replace the entire Marketplace window contents, on top of the normal contents. The normal contents are kept as they are, but out of view. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 220 +++++++++++---------- .../Marketplace/resources/qml/PackageDetails.qml | 10 + plugins/Marketplace/resources/qml/Packages.qml | 15 +- 3 files changed, 138 insertions(+), 107 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackageDetails.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index c04aa7eb6a..b293d21f92 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -42,143 +42,153 @@ Window anchors.fill: parent color: UM.Theme.getColor("main_background") - ColumnLayout + //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. + StackView { + id: contextStack anchors.fill: parent - spacing: UM.Theme.getSize("default_margin").height + initialItem: packageBrowse - // Page title. - Item + ColumnLayout { - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + id: packageBrowse + anchors.fill: parent - Label + spacing: UM.Theme.getSize("default_margin").height + + // Page title. + Item { - id: pageTitle - anchors + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + + Label { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - bottom: parent.bottom - } + id: pageTitle + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + bottom: parent.bottom + } - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + } } - } - // Search & Top-Level Tabs - Item - { - Layout.preferredHeight: childrenRect.height - Layout.preferredWidth: parent.width - 2 * UM.Theme.getSize("thin_margin").width - RowLayout + // Search & Top-Level Tabs + Item { - width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height - spacing: UM.Theme.getSize("thin_margin").width - - Rectangle + Layout.preferredHeight: childrenRect.height + Layout.preferredWidth: parent.width - 2 * UM.Theme.getSize("thin_margin").width + RowLayout { - color: "transparent" - Layout.preferredHeight: parent.height - Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 - Layout.fillWidth: ! searchBar.visible - } + width: parent.width + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("thin_margin").width - Cura.SearchBar - { - id: searchBar - Layout.preferredHeight: UM.Theme.getSize("button_icon").height - Layout.fillWidth: true - onTextEdited: searchStringChanged(text) - } + Rectangle + { + color: "transparent" + Layout.preferredHeight: parent.height + Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 + Layout.fillWidth: ! searchBar.visible + } - // Page selection. - TabBar - { - id: pageSelectionTabBar - anchors.right: parent.right - height: UM.Theme.getSize("button_icon").height - spacing: 0 - background: Rectangle { color: "transparent" } + Cura.SearchBar + { + id: searchBar + Layout.preferredHeight: UM.Theme.getSize("button_icon").height + Layout.fillWidth: true + onTextEdited: searchStringChanged(text) + } - PackageTypeTab + // Page selection. + TabBar { - id: pluginTabText - width: implicitWidth - text: catalog.i18nc("@button", "Plugins") - onClicked: + id: pageSelectionTabBar + anchors.right: parent.right + height: UM.Theme.getSize("button_icon").height + spacing: 0 + background: Rectangle { color: "transparent" } + + PackageTypeTab { - searchBar.text = "" - searchBar.visible = true - content.source = "Plugins.qml" + id: pluginTabText + width: implicitWidth + text: catalog.i18nc("@button", "Plugins") + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Plugins.qml" + } } - } - PackageTypeTab - { - id: materialsTabText - width: implicitWidth - text: catalog.i18nc("@button", "Materials") - onClicked: + PackageTypeTab { - searchBar.text = "" - searchBar.visible = true - content.source = "Materials.qml" + id: materialsTabText + width: implicitWidth + text: catalog.i18nc("@button", "Materials") + onClicked: + { + searchBar.text = "" + searchBar.visible = true + content.source = "Materials.qml" + } + } + ManagePackagesButton + { + onClicked: content.source = "ManagedPackages.qml" } } - ManagePackagesButton + + TextMetrics { - onClicked: content.source = "ManagedPackages.qml" + id: pluginTabTextMetrics + text: pluginTabText.text + font: pluginTabText.font + } + TextMetrics + { + id: materialsTabTextMetrics + text: materialsTabText.text + font: materialsTabText.font } } - - TextMetrics - { - id: pluginTabTextMetrics - text: pluginTabText.text - font: pluginTabText.font - } - TextMetrics - { - id: materialsTabTextMetrics - text: materialsTabText.text - font: materialsTabText.font - } } - } - - // Page contents. - Rectangle - { - Layout.preferredWidth: parent.width - Layout.fillHeight: true - color: UM.Theme.getColor("detail_background") // Page contents. - Loader + Rectangle { - id: content - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width - source: "Plugins.qml" + Layout.preferredWidth: parent.width + Layout.fillHeight: true + color: UM.Theme.getColor("detail_background") - Connections + // Page contents. + Loader { - target: content - function onLoaded() - { - pageTitle.text = content.item.pageTitle - searchStringChanged.connect(handleSearchStringChanged) - } - function handleSearchStringChanged(new_search) + id: content + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + source: "Plugins.qml" + + Connections { - content.item.model.searchString = new_search + target: content + function onLoaded() + { + pageTitle.text = content.item.pageTitle + searchStringChanged.connect(handleSearchStringChanged) + } + function handleSearchStringChanged(new_search) + { + content.item.model.searchString = new_search + } } } } diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml new file mode 100644 index 0000000000..4ad376e490 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -0,0 +1,10 @@ +// 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 + +Label +{ + text: "Test!" +} \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 79b4bf23a5..def8d40acb 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -62,9 +62,20 @@ ListView } } - delegate: PackageCard + delegate: MouseArea { - packageData: model.package + width: parent.width + height: childrenRect.height + + onClicked: + { + contextStack.push(Qt.resolvedUrl("PackageDetails.qml")) + } + + PackageCard + { + packageData: model.package + } } //Wrapper item to add spacing between content and footer. -- cgit v1.2.3 From 27d9118d177c32ed4c0d422344d4b6484000ab07 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 26 Nov 2021 16:48:02 +0100 Subject: Basis of header line I think the icon on the button is too small, but that's not currently configurable. Will have to look into that. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 41 ++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 4ad376e490..0dd196c26b 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -3,8 +3,45 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.3 -Label +import Cura 1.0 as Cura +import UM 1.0 as UM + +Item { - text: "Test!" + Column + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + RowLayout + { + spacing: UM.Theme.getSize("default_margin").width + + Cura.SecondaryButton + { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: UM.Theme.getSize("action_button").height + Layout.preferredWidth: height + + onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. + + tooltip: catalog.i18nc("@button:tooltip", "Back") + toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight + iconSource: UM.Theme.getIcon("ArrowLeft") + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: leftPadding + } + + Label + { + Layout.alignment: Qt.AlignVCenter + + text: "Install Plug-ins" //TODO: Depend on package type, and translate. + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + } + } + } } \ No newline at end of file -- cgit v1.2.3 From 04501f788c0e6bb6db30f46e5155759b6caa2031 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 11:57:12 +0100 Subject: Allow changing the icon size But use the same default as what was previously hard-coded. Now we can have buttons with non-standard icon sizes then, e.g. if the button size itself is also non-standard. Contributes to issue CURA-8565. --- resources/qml/ActionButton.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 62bea5df3b..f129e8c976 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -15,6 +15,7 @@ Button property bool isIconOnRightSide: false property alias iconSource: buttonIconLeft.source + property real iconSize: UM.Theme.getSize("action_button_icon").height property alias textFont: buttonText.font property alias cornerRadius: backgroundRect.radius property alias tooltip: tooltip.tooltipText @@ -158,7 +159,7 @@ Button { id: buttonIconRight source: buttonIconLeft.source - height: visible ? UM.Theme.getSize("action_button_icon").height : 0 + height: visible ? button.iconSize : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height -- cgit v1.2.3 From 0cd8798aff0133ee689d8105448f3d98f07518bf Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 11:59:46 +0100 Subject: Remove superfluous anchors.fill This is already set by the StackView, which (logically) requires that the children fill the entire space taken by the StackView. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index b293d21f92..951de77f19 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -53,7 +53,6 @@ Window ColumnLayout { id: packageBrowse - anchors.fill: parent spacing: UM.Theme.getSize("default_margin").height -- cgit v1.2.3 From d4ebf3baa06c36cca54b92cf149ffa7c1425f68f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:03:03 +0100 Subject: Also use proper icon size for left icon Almost forgot! Contributes to issue CURA-8565. --- resources/qml/ActionButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index f129e8c976..942c0ee578 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -110,7 +110,7 @@ Button { id: buttonIconLeft source: "" - height: visible ? UM.Theme.getSize("action_button_icon").height : 0 + height: visible ? button.iconSize : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height -- cgit v1.2.3 From cb7b9b319367508771f6185450716979104d51aa Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:03:29 +0100 Subject: Increase size of icon to fit button exactly Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 0dd196c26b..6cf29e9f45 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -29,9 +29,10 @@ Item tooltip: catalog.i18nc("@button:tooltip", "Back") toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight - iconSource: UM.Theme.getIcon("ArrowLeft") leftPadding: UM.Theme.getSize("narrow_margin").width rightPadding: leftPadding + iconSource: UM.Theme.getIcon("ArrowLeft") + iconSize: height - leftPadding * 2 } Label -- cgit v1.2.3 From d31079b7aa54f5080d55830b462553351a6f02fb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 12:57:36 +0100 Subject: Split page in header and contents This requires a small refactor here. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 79 ++++++++++++++-------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 6cf29e9f45..4b4b2123c7 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -10,39 +10,58 @@ import UM 1.0 as UM Item { - Column + RowLayout { - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width + id: header + anchors + { + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + } + + spacing: UM.Theme.getSize("default_margin").width + + Cura.SecondaryButton + { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: UM.Theme.getSize("action_button").height + Layout.preferredWidth: height + + onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. + + tooltip: catalog.i18nc("@button:tooltip", "Back") + toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: leftPadding + iconSource: UM.Theme.getIcon("ArrowLeft") + iconSize: height - leftPadding * 2 + } + + Label + { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true - RowLayout + text: "Install Plug-ins" //TODO: Depend on package type, and translate. + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + } + } + + Rectangle + { + anchors { - spacing: UM.Theme.getSize("default_margin").width - - Cura.SecondaryButton - { - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: UM.Theme.getSize("action_button").height - Layout.preferredWidth: height - - onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it. - - tooltip: catalog.i18nc("@button:tooltip", "Back") - toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignRight - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: leftPadding - iconSource: UM.Theme.getIcon("ArrowLeft") - iconSize: height - leftPadding * 2 - } - - Label - { - Layout.alignment: Qt.AlignVCenter - - text: "Install Plug-ins" //TODO: Depend on package type, and translate. - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - } + top: header.bottom + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + bottom: parent.bottom } + color: UM.Theme.getColor("detail_background") } } \ No newline at end of file -- cgit v1.2.3 From 5edd8302103621e1a821595a66951d051d3caf80 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 14:50:48 +0100 Subject: Add package card to detail page The card has the wrong layout, but it's a start. The data is communicated in any case. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 8 ++++++++ plugins/Marketplace/resources/qml/Packages.qml | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 4b4b2123c7..49e4d97633 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -10,6 +10,9 @@ import UM 1.0 as UM Item { + id: detailPage + property var packageData: packages.selectedPackage + RowLayout { id: header @@ -63,5 +66,10 @@ Item bottom: parent.bottom } color: UM.Theme.getColor("detail_background") + + PackageCard + { + packageData: detailPage.packageData + } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index def8d40acb..89254f2526 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -9,9 +9,10 @@ import UM 1.4 as UM ListView { id: packages + width: parent.width property string pageTitle - width: parent.width + property var selectedPackage clip: true @@ -69,7 +70,8 @@ ListView onClicked: { - contextStack.push(Qt.resolvedUrl("PackageDetails.qml")) + packages.selectedPackage = model.package; + contextStack.push(packageDetailsComponent); } PackageCard @@ -78,6 +80,16 @@ ListView } } + Component + { + id: packageDetailsComponent + + PackageDetails + { + packageData: packages.selectedPackage + } + } + //Wrapper item to add spacing between content and footer. footer: Item { -- cgit v1.2.3 From 0069182c6b8ee09cbc2fc7cd2f7dde09e9357a47 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:11:15 +0100 Subject: Set position and width of card in details page This means that the card itself shouldn't specify a width. It should get a width from how it's used. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 1 - plugins/Marketplace/resources/qml/PackageDetails.qml | 9 +++++++++ plugins/Marketplace/resources/qml/Packages.qml | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index f57facf0c0..448be8ff75 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,7 +12,6 @@ Rectangle { property var packageData - width: parent ? parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width: 0 height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 49e4d97633..7db46300a8 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -70,6 +70,15 @@ Item PackageCard { packageData: detailPage.packageData + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + } } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 89254f2526..47318d5a72 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -77,6 +77,7 @@ ListView PackageCard { packageData: model.package + width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width } } -- cgit v1.2.3 From 76e43759c7cf51b0a9d01a1ddd9309223b041e4c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:32:55 +0100 Subject: Restore check for parent before getting its width Delegates that are outside of the viewport may have no parent any more. Don't attempt to find their parents' width. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Packages.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 47318d5a72..49fca00fdb 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -65,7 +65,7 @@ ListView delegate: MouseArea { - width: parent.width + width: parent ? parent.width : 0 height: childrenRect.height onClicked: -- cgit v1.2.3 From 0b1b4ec01b08c47bcd0304fb9bbbcced14d0f7c3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 16:38:24 +0100 Subject: Communicate to PackageCard whether it is a detailed card or not If it is detailed, it currently hides the short description. That is not quite enough, but we'll expand that behaviour. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageCard.qml | 7 ++++--- plugins/Marketplace/resources/qml/PackageDetails.qml | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 448be8ff75..b8c053b5bb 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -11,6 +11,7 @@ import Cura 1.6 as Cura Rectangle { property var packageData + property bool expanded: false height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") @@ -21,7 +22,7 @@ Rectangle State { name: "Folded" - when: true // TODO + when: !expanded PropertyChanges { target: descriptionArea @@ -30,8 +31,8 @@ Rectangle }, State { - name: "Header" - when: false // TODO + name: "Expanded" + when: expanded PropertyChanges { target: descriptionArea diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 7db46300a8..c1ef765a14 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -69,7 +69,6 @@ Item PackageCard { - packageData: detailPage.packageData anchors { left: parent.left @@ -79,6 +78,9 @@ Item top: parent.top topMargin: UM.Theme.getSize("default_margin").height } + + packageData: detailPage.packageData + expanded: true } } } \ No newline at end of file -- cgit v1.2.3 From 39488823b124d281a5b9ff09d8c7fcaaebd73b41 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:37:54 +0100 Subject: Add forgotten ArrowLeft icon I've been using this for a while. It should've been included with the commit that added the button, but oh well. Contributes to issue CURA-8565. --- resources/themes/cura-light/icons/default/ArrowLeft.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/ArrowLeft.svg diff --git a/resources/themes/cura-light/icons/default/ArrowLeft.svg b/resources/themes/cura-light/icons/default/ArrowLeft.svg new file mode 100644 index 0000000000..d722b8ae8d --- /dev/null +++ b/resources/themes/cura-light/icons/default/ArrowLeft.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From b1af7ad203f766ce663ea2450b96397b088bafb8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 18:46:56 +0100 Subject: Add button to visit plug-in website There are a lot of buttons leading to websites now: An arrow leading to the author website. An author name leading to the author website. A 'read more' label leading to the plug-in website and this new button leading to the plug-in website. Maybe we should raise this with the designer. Contributes to issue CURA-8565. --- resources/themes/cura-light/icons/default/Globe.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/Globe.svg diff --git a/resources/themes/cura-light/icons/default/Globe.svg b/resources/themes/cura-light/icons/default/Globe.svg new file mode 100644 index 0000000000..4d955e9615 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Globe.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file -- cgit v1.2.3 From 2b419a23796bfede7b701bfaf235d128eb74b9f3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:04:30 +0100 Subject: Make package detail page scroll if details are too long Some plug-ins could have very long descriptions now. We show all of it, but that could go off the screen in theory. This makes the content scrollable if it goes off the screen. Contributes to issue CURA-8565. --- .../Marketplace/resources/qml/PackageDetails.qml | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index c1ef765a14..1ccb3dc4fd 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -67,20 +67,30 @@ Item } color: UM.Theme.getColor("detail_background") - PackageCard + ScrollView { - anchors + anchors.fill: parent + + clip: true //Need to clip, not for the bottom (which is off the window) but for the top (which would overlap the header). + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + contentHeight: expandedPackageCard.height + UM.Theme.getSize("default_margin").height * 2 + + PackageCard { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: anchors.leftMargin - top: parent.top - topMargin: UM.Theme.getSize("default_margin").height - } + id: expandedPackageCard + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: anchors.leftMargin + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + } - packageData: detailPage.packageData - expanded: true + packageData: detailPage.packageData + expanded: true + } } } } \ No newline at end of file -- cgit v1.2.3 From 02d74b4226ba26cdd7c68ffebfee0e81d9a03fd8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 29 Nov 2021 19:41:25 +0100 Subject: Make header of detail page depend on header of origin It's the same as the list of packages you came from, now. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/PackageDetails.qml | 3 ++- plugins/Marketplace/resources/qml/Packages.qml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index 1ccb3dc4fd..fdf1c8f92c 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -12,6 +12,7 @@ Item { id: detailPage property var packageData: packages.selectedPackage + property string title: catalog.i18nc("@header", "Package details") RowLayout { @@ -49,7 +50,7 @@ Item Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true - text: "Install Plug-ins" //TODO: Depend on package type, and translate. + text: detailPage.title font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") } diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 49fca00fdb..75207bc2e6 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -19,8 +19,6 @@ ListView Component.onCompleted: model.updatePackages() Component.onDestruction: model.abortUpdating() - //ScrollBar.vertical.policy: ScrollBar.AlwaysOff - spacing: UM.Theme.getSize("default_margin").height section.property: "package.sectionTitle" @@ -88,6 +86,7 @@ ListView PackageDetails { packageData: packages.selectedPackage + title: packages.pageTitle } } -- cgit v1.2.3 From eb156f114c7d596bf778c48dfee7ff9ddfb7bcff Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 30 Nov 2021 12:03:15 +0100 Subject: Introduce a ManageButton The state and styling of this coupled with the available options. Discussed with UX that primary state: Install, Enable, Update while secondary states are: Uninstall, Disable Each primary/secondary state also has a busy state, with the verb and spinner. Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 55 +++++--- plugins/Marketplace/resources/qml/ManageButton.qml | 154 +++++++++++++++++++++ 2 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/ManageButton.qml diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 24f9671b34..4a4973a2dc 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -99,34 +99,45 @@ class PackageModel(QObject): def sectionTitle(self) -> Optional[str]: return self._section_title - enableManageButtonChanged = pyqtSignal() + isInstalledChanged = pyqtSignal() - @pyqtProperty(str, notify = enableManageButtonChanged) - def enableManageButtonText(self): - if self._is_active: - return catalog.i18nc("@button", "Disable") - else: - return catalog.i18nc("@button", "Enable") - - @pyqtProperty(bool, notify = enableManageButtonChanged) - def enableManageButtonVisible(self): + @pyqtProperty(bool, notify = isInstalledChanged) + def isInstalled(self): return self._is_installed - installManageButtonChanged = pyqtSignal() + isEnabledChanged = pyqtSignal() + + @pyqtProperty(bool, notify = isEnabledChanged) + def isEnabled(self): + return self._is_active + + manageEnableStateChanged = pyqtSignal() - @pyqtProperty(str, notify = installManageButtonChanged) - def installManageButtonText(self): + @pyqtProperty(str, notify = manageEnableStateChanged) + def manageEnableState(self): + # TODO: Handle manual installed packages if self._is_installed: - return catalog.i18nc("@button", "Uninstall") + if self._is_active: + return "secondary" + else: + return "primary" else: - return catalog.i18nc("@button", "Install") + return "hidden" - @pyqtProperty(bool, notify = installManageButtonChanged) - def installManageButtonVisible(self): - return not self._is_bundled + manageInstallStateChanged = pyqtSignal() + + @pyqtProperty(str, notify = manageInstallStateChanged) + def manageInstallState(self): + if self._is_installed: + if self._is_bundled: + return "hidden" + else: + return "secondary" + else: + return "primary" - updateManageButtonChanged = pyqtSignal() + manageUpdateStateChanged = pyqtSignal() - @pyqtProperty(bool, notify = updateManageButtonChanged) - def updateManageButtonVisible(self): - return False # Todo: implement + @pyqtProperty(str, notify = manageUpdateStateChanged) + def manageUpdateState(self): + return "hidden" # TODO: implement diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml new file mode 100644 index 0000000000..e58347124a --- /dev/null +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -0,0 +1,154 @@ +// 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 + +RowLayout +{ + id: manageButton + property alias primaryText: primaryButton.text + property alias secondaryText: secondaryButton.text + property string busyPrimaryText: busyMessageText.text + property string busySecondaryText: busyMessageText.text + property string mainState: "primary" + + state: mainState + + Cura.PrimaryButton + { + id: primaryButton + visible: false + + onClicked: + { + manageButton.state = "busy" + } + } + + Cura.SecondaryButton + { + id: secondaryButton + visible: false + + onClicked: + { + manageButton.state = "busy" + } + } + + Item + { + id: busyMessage + visible: false + height: UM.Theme.getSize("action_button").height + width: childrenRect.width + + BusyIndicator + { + id: busyIndicator + visible: parent.visible + width: height + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + palette.dark: UM.Theme.getColor("text") + + RotationAnimator + { + target: busyIndicator.contentItem + running: busyIndicator.visible && busyIndicator.running + from: 0 + to: 360 + loops: Animation.Infinite + duration: 2500 + } + } + Label + { + id: busyMessageText + visible: parent.visible + text: manageButton.mainState == "primary" ? manageButton.busyPrimaryText : manageButton.busySecondaryText + anchors.left: busyIndicator.right + anchors.verticalCenter: parent.verticalCenter + + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } + } + + states: + [ + State + { + name: "primary" + PropertyChanges + { + target: primaryButton + visible: true + } + PropertyChanges + { + target: secondaryButton + visible: false + } + PropertyChanges + { + target: busyMessage + visible: false + } + }, + State + { + name: "secondary" + PropertyChanges + { + target: primaryButton + visible: false + } + PropertyChanges + { + target: secondaryButton + visible: true + } + PropertyChanges + { + target: busyMessage + visible: false + } + }, + State + { + name: "hidden" + PropertyChanges + { + target: manageButton + visible: false + } + }, + State + { + name: "busy" + PropertyChanges + { + target: primaryButton + visible: false + } + PropertyChanges + { + target: secondaryButton + visible: false + } + PropertyChanges + { + target: busyMessage + visible: true + } + } + ] +} -- cgit v1.2.3 From 6514fdf9f294b7822370e57ba446170710c7afc3 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 30 Nov 2021 15:40:18 +0100 Subject: Check the server if a package can be updated The server is queried using the local packages_id and version to determine if they can be updated. The Manage button state is set accordingly. This is done in an async way to keep the UI responsive A.t.m I'm not sure if I might need to move this logic out, since we also need to make this query when check periodically for updates. when the list is not shown. But that would also entail creating the installed packages list before the Manage Packages tab is ever created in the Marketplace. Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 33 +++++++++++++++++++++++++++++++- plugins/Marketplace/Marketplace.py | 1 + plugins/Marketplace/PackageList.py | 8 ++++++++ plugins/Marketplace/RemotePackageList.py | 8 +------- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 6acbaa8500..059dd9bbef 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -8,11 +8,14 @@ if TYPE_CHECKING: from PyQt5.QtCore import QObject from UM.i18n import i18nCatalog +from UM.TaskManagement.HttpRequestManager import HttpRequestManager +from UM.Logger import Logger from cura.CuraApplication import CuraApplication from .PackageList import PackageList -from .PackageModel import PackageModel # The contents of this list. +from .PackageModel import PackageModel +from . import Marketplace catalog = i18nCatalog("cura") @@ -55,6 +58,7 @@ class LocalPackageList(PackageList): for the sections are sorted alphabetically on the display name. These sorted sections are then added to the items """ package_info = list(self._allPackageInfo()) + self.checkForUpdates(package_info) sorted_sections: List[Dict[str, PackageModel]] = [] for section in self._getSections(): packages = filter(lambda p: p.sectionTitle == section, package_info) @@ -91,3 +95,30 @@ class LocalPackageList(PackageList): package_type = package_info["package_type"] section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] return PackageModel(package_info, section_title = section_title, parent = self) + + def checkForUpdates(self, packages: List[PackageModel]): + installed_packages = "installed_packages=".join([f"{package.packageId}:{package.packageVersion}&" for package in packages]) + request_url = f"{Marketplace.PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" + + self._ongoing_request = HttpRequestManager.getInstance().get( + request_url, + scope = self._scope, + callback = self._parseResponse + ) + return [] + + def _parseResponse(self, reply: "QNetworkReply") -> None: + """ + Parse the response from the package list API request which can update. + + :param reply: A reply containing information about a number of packages. + """ + response_data = HttpRequestManager.readJSON(reply) + if "data" not in response_data: + Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") + self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) + return + + for package_data in response_data["data"]: + index = self.find("package", package_data["package_id"]) + self.getItem(index)["package"].canUpdate = True diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 18d80d6e68..1b98503969 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. +PACKAGE_UPDATES_URL = f"{PACKAGES_URL}/package-updates" # URL to use for requesting the list of packages that can be updated. class Marketplace(Extension): diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 8171d168f2..d3da8c826a 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -6,6 +6,11 @@ from typing import Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel +from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. +from UM.TaskManagement.HttpRequestManager import HttpRequestData # To request the package list from the API. + +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -27,6 +32,9 @@ class PackageList(ListModel): self._has_more = False self._has_footer = True + self._ongoing_request: Optional[HttpRequestData] = None + self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + @pyqtSlot() def updatePackages(self) -> None: """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index e7df498fbf..4fedde9ff0 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -5,12 +5,9 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkReply from typing import Optional, TYPE_CHECKING -from cura.CuraApplication import CuraApplication -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from UM.i18n import i18nCatalog from UM.Logger import Logger -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 UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. from .PackageList import PackageList @@ -28,9 +25,6 @@ class RemotePackageList(PackageList): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._ongoing_request: Optional[HttpRequestData] = None - self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) - self._package_type_filter = "" self._requested_search_string = "" self._current_search_string = "" -- cgit v1.2.3 From caa8da69b4037edd7390c18704f2abb7f3855627 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 1 Dec 2021 16:46:37 +0100 Subject: Fix section mismatch after package order changed Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/Packages.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 75207bc2e6..f0d6f508cc 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -29,17 +29,16 @@ ListView color: UM.Theme.getColor("detail_background") - required property string section - Label { id: sectionHeaderText anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - text: parent.section + text: section font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") + onTextChanged: print(text) } } @@ -221,4 +220,3 @@ ListView } } } - -- cgit v1.2.3 From 66e52294b5b0f1beb6c706698588ff074b33ff95 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 1 Dec 2021 17:08:08 +0100 Subject: Get the locally installed packages Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 26d6591099..4199375608 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,13 +1,16 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import List, Tuple, TYPE_CHECKING, Optional +from typing import Any, Dict, List, Tuple, TYPE_CHECKING, Optional +from collections import Generator from cura.CuraApplication import CuraApplication #To find some resource types. from cura.Settings.GlobalStack import GlobalStack from UM.PackageManager import PackageManager #The class we're extending. from UM.Resources import Resources #To find storage paths for some resource types. +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") if TYPE_CHECKING: from UM.Qt.QtApplication import QtApplication @@ -17,6 +20,18 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) + self._locally_installed_packages = None + + @property + def locally_installed_packages(self): + """locally installed packages, lazy execution""" + if self._locally_installed_packages is None: + self._locally_installed_packages = list(self.iterateAllLocalPackages()) + return self._locally_installed_packages + + @locally_installed_packages.setter + def locally_installed_packages(self, value): + self._locally_installed_packages = value def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) @@ -47,3 +62,20 @@ class CuraPackageManager(PackageManager): machine_with_qualities.append((global_stack, str(extruder_nr), container_id)) return machine_with_materials, machine_with_qualities + + def iterateAllLocalPackages(self) -> Generator[Dict[str, Any]]: + """ A generator which returns an unordered list of all the PackageModels""" + + # Get all the installed packages, add a section_title depending on package_type and user installed + for packages in self.getAllInstalledPackagesInfo().values(): + for package_info in packages: + yield package_info + + # Get all to be removed package_info's. These packages are still used in the current session so the user might + # still want to interact with these. + for package_data in self.getPackagesToRemove().values(): + yield package_data["package_info"] + + # Get all to be installed package_info's. Since the user might want to interact with these + for package_data in self.getPackagesToInstall().values(): + yield package_data["package_info"] -- cgit v1.2.3 From 09bc28d840165649e7e1b5e0ae41d5df25cc42b5 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 1 Dec 2021 17:09:52 +0100 Subject: Moved local package logic out of the LocalPackageList Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 74 ++++++++++----------------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 059dd9bbef..d93ed87ba0 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,7 +1,9 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING +from operator import attrgetter + from PyQt5.QtCore import pyqtSlot, QObject if TYPE_CHECKING: @@ -21,16 +23,16 @@ catalog = i18nCatalog("cura") class LocalPackageList(PackageList): - PACKAGE_SECTION_HEADER = { + PACKAGE_CATEGORIES = { "installed": { - "plugin": catalog.i18nc("@label:property", "Installed Plugins"), - "material": catalog.i18nc("@label:property", "Installed Materials") + "plugin": catalog.i18nc("@label", "Installed Plugins"), + "material": catalog.i18nc("@label", "Installed Materials") }, "bundled": { - "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), - "material": catalog.i18nc("@label:property", "Bundled Materials") + "plugin": catalog.i18nc("@label", "Bundled Plugins"), + "material": catalog.i18nc("@label", "Bundled Materials") } } # The section headers to be used for the different package categories @@ -47,57 +49,24 @@ class LocalPackageList(PackageList): """ self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) - self._getLocalPackages() - self.setIsLoading(False) - self.setHasMore(False) # All packages should have been loaded at this time - def _getLocalPackages(self) -> None: - """ Obtain the local packages. + # Obtain and sort the local packages + self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.locally_installed_packages]]) + self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) + self.checkForUpdates(self._manager.locally_installed_packages) - The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages - for the sections are sorted alphabetically on the display name. These sorted sections are then added to the items - """ - package_info = list(self._allPackageInfo()) - self.checkForUpdates(package_info) - sorted_sections: List[Dict[str, PackageModel]] = [] - for section in self._getSections(): - packages = filter(lambda p: p.sectionTitle == section, package_info) - sorted_sections.extend([{"package": p} for p in sorted(packages, key = lambda p: p.displayName)]) - self.setItems(sorted_sections) - - def _getSections(self) -> Generator[str, None, None]: - """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the - correct order""" - for package_type in self.PACKAGE_SECTION_HEADER.values(): - for section in package_type.values(): - yield section - - def _allPackageInfo(self) -> Generator[PackageModel, None, None]: - """ A generator which returns a unordered list of all the PackageModels""" - - # Get all the installed packages, add a section_title depending on package_type and user installed - for packages in self._manager.getAllInstalledPackagesInfo().values(): - for package_info in packages: - yield self._makePackageModel(package_info) - - # Get all to be removed package_info's. These packages are still used in the current session so the user might - # still want to interact with these. - for package_data in self._manager.getPackagesToRemove().values(): - yield self._makePackageModel(package_data["package_info"]) - - # Get all to be installed package_info's. Since the user might want to interact with these - for package_data in self._manager.getPackagesToInstall().values(): - yield self._makePackageModel(package_data["package_info"]) + self.setIsLoading(False) + self.setHasMore(False) # All packages should have been loaded at this time def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel: """ Create a PackageModel from the package_info and determine its section_title""" bundled_or_installed = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" package_type = package_info["package_type"] - section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] + section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type] return PackageModel(package_info, section_title = section_title, parent = self) - def checkForUpdates(self, packages: List[PackageModel]): - installed_packages = "installed_packages=".join([f"{package.packageId}:{package.packageVersion}&" for package in packages]) + def checkForUpdates(self, packages: List[Dict[str, Any]]): + installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) request_url = f"{Marketplace.PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" self._ongoing_request = HttpRequestManager.getInstance().get( @@ -105,7 +74,6 @@ class LocalPackageList(PackageList): scope = self._scope, callback = self._parseResponse ) - return [] def _parseResponse(self, reply: "QNetworkReply") -> None: """ @@ -115,10 +83,14 @@ class LocalPackageList(PackageList): """ response_data = HttpRequestManager.readJSON(reply) if "data" not in response_data: - Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") - self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) + Logger.error( + f"Could not interpret the server's response. Missing 'data' from response data. Keys in response: {response_data.keys()}") + return + if len(response_data["data"]) == 0: return for package_data in response_data["data"]: index = self.find("package", package_data["package_id"]) self.getItem(index)["package"].canUpdate = True + + self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) -- cgit v1.2.3 From 02e2e0a1c6bf71b29a3198a9beb78266ea241c64 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 1 Dec 2021 17:35:56 +0100 Subject: Filter already installed packages from the install listviews Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 5 ++--- plugins/Marketplace/LocalPackageList.py | 3 --- plugins/Marketplace/PackageList.py | 1 + plugins/Marketplace/RemotePackageList.py | 3 +++ 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 4199375608..a8400bfae7 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,8 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict, List, Tuple, TYPE_CHECKING, Optional -from collections import Generator +from typing import Any, Dict, List, Tuple, TYPE_CHECKING, Optional, Generator from cura.CuraApplication import CuraApplication #To find some resource types. from cura.Settings.GlobalStack import GlobalStack @@ -63,7 +62,7 @@ class CuraPackageManager(PackageManager): return machine_with_materials, machine_with_qualities - def iterateAllLocalPackages(self) -> Generator[Dict[str, Any]]: + def iterateAllLocalPackages(self) -> Generator[Dict[str, Any], None, None]: """ A generator which returns an unordered list of all the PackageModels""" # Get all the installed packages, add a section_title depending on package_type and user installed diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index d93ed87ba0..bbe74a9056 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -13,8 +13,6 @@ from UM.i18n import i18nCatalog from UM.TaskManagement.HttpRequestManager import HttpRequestManager from UM.Logger import Logger -from cura.CuraApplication import CuraApplication - from .PackageList import PackageList from .PackageModel import PackageModel from . import Marketplace @@ -38,7 +36,6 @@ class LocalPackageList(PackageList): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._manager = CuraApplication.getInstance().getPackageManager() self._has_footer = False @pyqtSlot() diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index d3da8c826a..798a15324e 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -26,6 +26,7 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) + self._manager = CuraApplication.getInstance().getPackageManager() self._error_message = "" self.addRoleName(self.PackageRole, "package") self._is_loading = False diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 4fedde9ff0..d5c0763609 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -31,6 +31,7 @@ class RemotePackageList(PackageList): self._request_url = self._initialRequestUrl() self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() + self._locally_installed = { p["package_id"] for p in self._manager.locally_installed_packages } def __del__(self) -> None: """ @@ -128,6 +129,8 @@ class RemotePackageList(PackageList): return for package_data in response_data["data"]: + if package_data["package_id"] in self._locally_installed: + continue # We should only show packages which are not already installed try: package = PackageModel(package_data, parent = self) self.appendItem({"package": package}) # Add it to this list model. -- cgit v1.2.3 From ecc3760e8e2e5b964b22ee47b120e54a287b9ede Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 13:24:12 +0100 Subject: Increase size of icons on action buttons We want those to be 1.5em now. This has an effect on all action buttons with icons in the interface! Contributes to issue CURA-8565. --- resources/themes/cura-light/theme.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index dede26db0b..4bc6adc99c 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -563,7 +563,7 @@ "button_lining": [0, 0], "action_button": [15.0, 2.5], - "action_button_icon": [1.0, 1.0], + "action_button_icon": [1.5, 1.5], "action_button_radius": [0.15, 0.15], "dialog_primary_button_padding": [3.0, 0], -- cgit v1.2.3 From 8162bdcfa82a9b39e4ede12dfa37179cb8710735 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:42:12 +0100 Subject: Don't show download count for bundled plug-ins I considered rewriting the section title property to be QML-only and translate it from the QML, but this is a bit simpler in the end, even though there is data duplication now. Contributes to issue CURA-8565. --- plugins/Marketplace/PackageModel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 4a4973a2dc..62558a51b8 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -17,10 +17,11 @@ class PackageModel(QObject): QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: + def __init__(self, package_data: Dict[str, Any], installation_status: str, section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: """ Constructs a new model for a single package. :param package_data: The data received from the Marketplace API about the package to create. + :param installation_status: Whether the package is `not_installed`, `installed` or `bundled`. :param section_title: If the packages are to be categorized per section provide the section_title :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ @@ -48,6 +49,7 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") + self._installation_status = installation_status self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. @@ -95,6 +97,10 @@ class PackageModel(QObject): def authorInfoUrl(self): return self._author_info_url + @pyqtProperty(str, constant = True) + def installationStatus(self) -> str: + return self._installation_status + @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title -- cgit v1.2.3 From 3a284e3c2c4297fbcec325be8eb2d5972c3797bc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:50:45 +0100 Subject: Show hover colour when hovering a card This signals to the user they can select one. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Packages.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index f0d6f508cc..3c627aef32 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -62,9 +62,11 @@ ListView delegate: MouseArea { + id: cardMouseArea width: parent ? parent.width : 0 height: childrenRect.height + hoverEnabled: true onClicked: { packages.selectedPackage = model.package; @@ -75,6 +77,7 @@ ListView { packageData: model.package width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width + color: cardMouseArea.containsMouse ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("main_background") } } -- cgit v1.2.3 From 6f65521ce88c8cdbbad9eea37273a0d0e504a29c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 14:56:09 +0100 Subject: Make icons smaller for tertiary buttons These are typically visually smaller buttons, since they don't have an outline. It makes more sense to use the size of the text then, or something thereabouts. Contributes to issue CURA-8565. --- resources/qml/TertiaryButton.qml | 1 + resources/themes/cura-light/theme.json | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/qml/TertiaryButton.qml b/resources/qml/TertiaryButton.qml index 76684b6ef2..8171188232 100644 --- a/resources/qml/TertiaryButton.qml +++ b/resources/qml/TertiaryButton.qml @@ -16,4 +16,5 @@ Cura.ActionButton textDisabledColor: UM.Theme.getColor("action_button_disabled_text") hoverColor: "transparent" underlineTextOnHover: true + iconSize: UM.Theme.getSize("action_button_icon_small").height } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 4bc6adc99c..6dec15aff8 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -564,6 +564,7 @@ "action_button": [15.0, 2.5], "action_button_icon": [1.5, 1.5], + "action_button_icon_small": [1.0, 1.0], "action_button_radius": [0.15, 0.15], "dialog_primary_button_padding": [3.0, 0], -- cgit v1.2.3 From 7fe327fb483ca58b1d2a47409d5e322c39841f51 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 15:57:44 +0100 Subject: Fix resetting when Marketplace is closed and re-opened Previously, this would cause the Marketplace to freeze. We're still not entirely sure why. It seems to be a bug in Qt, but it's rather hard to deal with. This new solution is nicer in some ways but not as neat in others. - We're no longer clearing the content of the loader, so the QML and the package data remains in memory while the Marketplace is closed. We deem this to not be a problem, because the memory usage of this package data is only a couple of kB, nothing compared to the memory used by the slicer when it loads a model. - On the other hand, it's now possible to programmatically change the tab there, instead of manually having to click the buttons. - Fixes a bug where the highlighted tab of of the tab bar doesn't update when closing and re-opening the Marketplace. And a bug where there was a search bar for the manage page while it didn't work. Contributes to issue CURA-8565. --- plugins/Marketplace/resources/qml/Marketplace.qml | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 951de77f19..a02c0e0d5f 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -21,8 +21,14 @@ Window width: minimumWidth height: minimumHeight - // Set and unset the content. No need to keep things in memory if it's not visible. - onVisibleChanged: content.source = visible ? "Plugins.qml" : "" + onVisibleChanged: + { + pageSelectionTabBar.currentIndex = 0; //Go back to the initial tab. + while(contextStack.depth > 1) + { + contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? + } + } Connections { @@ -116,33 +122,33 @@ Window spacing: 0 background: Rectangle { color: "transparent" } + onCurrentIndexChanged: + { + searchBar.text = ""; + searchBar.visible = currentItem.hasSearch; + content.source = currentItem.sourcePage; + } + PackageTypeTab { id: pluginTabText width: implicitWidth text: catalog.i18nc("@button", "Plugins") - onClicked: - { - searchBar.text = "" - searchBar.visible = true - content.source = "Plugins.qml" - } + property string sourcePage: "Plugins.qml" + property bool hasSearch: true } PackageTypeTab { id: materialsTabText width: implicitWidth text: catalog.i18nc("@button", "Materials") - onClicked: - { - searchBar.text = "" - searchBar.visible = true - content.source = "Materials.qml" - } + property string sourcePage: "Materials.qml" + property bool hasSearch: true } ManagePackagesButton { - onClicked: content.source = "ManagedPackages.qml" + property string sourcePage: "ManagedPackages.qml" + property bool hasSearch: false } } -- cgit v1.2.3 From 9a1fbd54b03e65279b599273c05626a873f8caa5 Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 10:06:50 +0100 Subject: Add basic onboarding banner to the market place --- .../Marketplace/resources/qml/OnboardBanner.qml | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 plugins/Marketplace/resources/qml/OnboardBanner.qml diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml new file mode 100644 index 0000000000..b91a9a52f7 --- /dev/null +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -0,0 +1,79 @@ +// 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 + +// Onboarding banner. +Rectangle +{ + Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height + anchors + { + margins: UM.Theme.getSize("default_margin").width + left: parent.left + right: parent.right + top: parent.top + } + + color: UM.Theme.getColor("action_panel_secondary") + + // Icon + Rectangle + { + id: onboardingIcon + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("button_icon").width + height: UM.Theme.getSize("button_icon").height + color: "transparent" + UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary_text") + source: UM.Theme.getIcon("Shop") + } + } + + // Close button + UM.SimpleButton + { + id: onboardingClose + anchors + { + top: parent.top + right: parent.right + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("message_close").width + height: UM.Theme.getSize("message_close").height + color: UM.Theme.getColor("primary_text") + hoverColor: UM.Theme.getColor("primary_text_hover") + iconSource: UM.Theme.getIcon("Cancel") + onClicked: confirmDeleteDialog.visible = true + } + + // Body + Text { + anchors + { + top: parent.top + left: onboardingIcon.right + right: onboardingClose.left + margins: UM.Theme.getSize("default_margin").width + } + + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("primary_text") + wrapMode: Text.WordWrap + text: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + } +} \ No newline at end of file -- cgit v1.2.3 From 5e4f67c7e1f3ee62e51afcd7738bf2edcb4d2f7e Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 10:10:09 +0100 Subject: Display different content on each of the marketplace onboarding banners --- .../Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/OnboardBanner.qml | 20 ++++++++++++++++++-- plugins/Marketplace/resources/qml/Plugins.qml | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 243d5bf12e..c53384bf77 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -10,6 +10,7 @@ import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Manage packages") + bannerType: "__MANAGE_PACKAGES__" model: Marketplace.LocalPackageList { } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 1d1572976a..dff63305bf 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -6,6 +6,7 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Materials") + bannerType: "__MATERIALS__" model: Marketplace.RemotePackageList { packageTypeFilter: "material" diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index b91a9a52f7..8a1048018c 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,6 +11,8 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { + property var bannerType + Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors { @@ -39,7 +41,14 @@ Rectangle { anchors.fill: parent color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon("Shop") + source: { + switch (bannerType) { + case "__PLUGINS__" : return UM.Theme.getIcon("Shop"); + case "__MATERIALS__" : return UM.Theme.getIcon("Spool"); + case "__MANAGE_PACKAGES__" : return UM.Theme.getIcon("ArrowDoubleCircleRight"); + default: return ""; + } + } } } @@ -74,6 +83,13 @@ Rectangle font: UM.Theme.getFont("medium") color: UM.Theme.getColor("primary_text") wrapMode: Text.WordWrap - text: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + text: { + switch (bannerType) { + case "__PLUGINS__" : return catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users."); + case "__MATERIALS__" : return catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers."); + case "__MANAGE_PACKAGES__" : return catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly."); + default: return ""; + } + } } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index ef5d92c2e8..24381e3027 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -6,6 +6,7 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") + bannerType: "__PLUGINS__" model: Marketplace.RemotePackageList { packageTypeFilter: "plugin" -- cgit v1.2.3 From 748101ce69932f5d8f82ef27bee6b33a887779e5 Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 26 Nov 2021 14:49:00 +0100 Subject: Remove banners when clicking close button --- cura/CuraApplication.py | 31 ++++++++++++++++++++++ .../Marketplace/resources/qml/ManagedPackages.qml | 10 ++++++- plugins/Marketplace/resources/qml/Materials.qml | 10 ++++++- .../Marketplace/resources/qml/OnboardBanner.qml | 28 +++++++------------ plugins/Marketplace/resources/qml/Plugins.qml | 10 ++++++- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3d4ec1209f..6cf2593bbf 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -572,6 +572,10 @@ class CuraApplication(QtApplication): preferences.addPreference("general/accepted_user_agreement", False) + preferences.addPreference("cura/market_place_show_plugin_banner", True) + preferences.addPreference("cura/market_place_show_material_banner", True) + preferences.addPreference("cura/market_place_show_manage_packages_banner", True) + for key in [ "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_profile_path", @@ -2011,6 +2015,33 @@ class CuraApplication(QtApplication): show_whatsnew_only = has_active_machine and has_app_just_upgraded return show_whatsnew_only + @pyqtSlot(result = bool) + def shouldShowMarketPlacePluginBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_plugin_banner") + + @pyqtSlot(result = bool) + def shouldShowMarketPlaceMaterialBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_material_banner") + + @pyqtSlot(result = bool) + def shouldShowMarketPlaceManagePackagesBanner(self) -> bool: + return self._preferences.getValue("cura/market_place_show_manage_packages_banner") + + @pyqtSlot() + def closeMarketPlacePluginBanner(self) -> None: + Logger.log("i", "Close market place plugin banner") + self._preferences.setValue("cura/market_place_show_plugin_banner", False) + + @pyqtSlot() + def closeMarketPlaceMaterialBanner(self) -> None: + Logger.log("i", "Close market place material banner") + self._preferences.setValue("cura/market_place_show_material_banner", False) + + @pyqtSlot() + def closeMarketPlaceManagePackagesBanner(self) -> None: + Logger.log("i", "Close market place manage packages banner") + self._preferences.setValue("cura/market_place_show_manage_packages_banner", False) + @pyqtSlot(result = int) def appWidth(self) -> int: main_window = QtApplication.getInstance().getMainWindow() diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index c53384bf77..a329d992e5 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -10,7 +10,15 @@ import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Manage packages") - bannerType: "__MANAGE_PACKAGES__" + + bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() + bannerIcon: "ArrowDoubleCircleRight" + bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlaceManagePackagesBanner(); + bannerVisible = false; + } + model: Marketplace.LocalPackageList { } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index dff63305bf..32d2c2213a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -6,7 +6,15 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Materials") - bannerType: "__MATERIALS__" + + bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() + bannerIcon: "Spool" + bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlaceMaterialBanner(); + bannerVisible = false; + } + model: Marketplace.RemotePackageList { packageTypeFilter: "material" diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8a1048018c..8a29030514 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,7 +11,12 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property var bannerType + property bool bannerVisible + property string bannerIcon + property string bannerBody + property var onRemoveBanner + + visible: bannerVisible Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors @@ -41,14 +46,7 @@ Rectangle { anchors.fill: parent color: UM.Theme.getColor("primary_text") - source: { - switch (bannerType) { - case "__PLUGINS__" : return UM.Theme.getIcon("Shop"); - case "__MATERIALS__" : return UM.Theme.getIcon("Spool"); - case "__MANAGE_PACKAGES__" : return UM.Theme.getIcon("ArrowDoubleCircleRight"); - default: return ""; - } - } + source: UM.Theme.getIcon(bannerIcon) } } @@ -67,7 +65,8 @@ Rectangle color: UM.Theme.getColor("primary_text") hoverColor: UM.Theme.getColor("primary_text_hover") iconSource: UM.Theme.getIcon("Cancel") - onClicked: confirmDeleteDialog.visible = true + + onClicked: onRemoveBanner() } // Body @@ -83,13 +82,6 @@ Rectangle font: UM.Theme.getFont("medium") color: UM.Theme.getColor("primary_text") wrapMode: Text.WordWrap - text: { - switch (bannerType) { - case "__PLUGINS__" : return catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users."); - case "__MATERIALS__" : return catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers."); - case "__MANAGE_PACKAGES__" : return catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly."); - default: return ""; - } - } + text: bannerBody } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 24381e3027..b9d38c6e2b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -6,7 +6,15 @@ import Marketplace 1.0 as Marketplace Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") - bannerType: "__PLUGINS__" + + bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() + bannerIcon: "Shop" + bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + onRemoveBanner: function() { + CuraApplication.closeMarketPlacePluginBanner(); + bannerVisible = false; + } + model: Marketplace.RemotePackageList { packageTypeFilter: "plugin" -- cgit v1.2.3 From b75ba44a94e78fda80ad3becfe8a6e8cacc17e72 Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:31:34 +0100 Subject: Add "readmore" button --- .../Marketplace/resources/qml/OnboardBanner.qml | 55 ++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8a29030514..150377eaf3 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -70,7 +70,8 @@ Rectangle } // Body - Text { + Label { + id: infoText anchors { top: parent.top @@ -80,8 +81,56 @@ Rectangle } font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("primary_text") - wrapMode: Text.WordWrap text: bannerBody + + renderType: Text.NativeRendering + color: "white" + wrapMode: Text.Wrap + elide: Text.ElideRight + + onLineLaidOut: + { + if(line.isLast) + { + // Check if read more button still fits after the body text + if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) + { + // If it does place it after the body text + readMoreButton.anchors.left = infoText.left; + readMoreButton.anchors.bottom = infoText.bottom; + readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); + readMoreButton.anchors.leftMargin = 0; + } + else + { + // Otherwise place it under the text + readMoreButton.anchors.left = infoText.left; + readMoreButton.anchors.bottom = infoText.bottom; + readMoreButton.anchors.leftMargin = line.implicitWidth + UM.Theme.getSize("default_margin").width; + readMoreButton.anchors.bottomMargin = 0; + } + } + } + } + + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } + + Cura.TertiaryButton + { + id: readMoreButton + text: "Learn More" + textFont: UM.Theme.getFont("default") + textColor: infoText.color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + height: fontMetrics.height + + onClicked: print("TODO") } } \ No newline at end of file -- cgit v1.2.3 From a4d9beb9c2ceb8d1897f4771bd9b868b582becce Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:31:47 +0100 Subject: Change font size in banner --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 150377eaf3..cbd151bed2 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -80,7 +80,7 @@ Rectangle margins: UM.Theme.getSize("default_margin").width } - font: UM.Theme.getFont("medium") + font: UM.Theme.getFont("default") text: bannerBody renderType: Text.NativeRendering -- cgit v1.2.3 From 815aadb0d76779735df0d0a3b5e5a2d5bb141d88 Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 27 Nov 2021 13:48:38 +0100 Subject: Add links to read me support pages Note that since the support pages are not yet ready the "read more" buttons are hidden. Once the correct link is added they become visible again. --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 +++- plugins/Marketplace/resources/qml/Plugins.qml | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index a329d992e5..e8e54d77b5 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -14,6 +14,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() bannerIcon: "ArrowDoubleCircleRight" bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlaceManagePackagesBanner(); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 32d2c2213a..fc4869dac5 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,6 +10,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() bannerIcon: "Spool" bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlaceMaterialBanner(); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index cbd151bed2..4b5e494be5 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -15,6 +15,7 @@ Rectangle property string bannerIcon property string bannerBody property var onRemoveBanner + property string readMoreUrl visible: bannerVisible @@ -121,6 +122,7 @@ Rectangle Cura.TertiaryButton { + visible: readMoreUrl !== "" id: readMoreButton text: "Learn More" textFont: UM.Theme.getFont("default") @@ -131,6 +133,6 @@ Rectangle isIconOnRightSide: true height: fontMetrics.height - onClicked: print("TODO") + onClicked: Qt.openUrlExternally(readMoreUrl) } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index b9d38c6e2b..f0c101153c 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,6 +10,7 @@ Packages bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() bannerIcon: "Shop" bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { CuraApplication.closeMarketPlacePluginBanner(); bannerVisible = false; -- cgit v1.2.3 From 7e1247b171dc3cf99cf704734778218badf8fceb Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:50:52 +0100 Subject: Directly use bindings in banner pages CURA-8564 --- cura/CuraApplication.py | 27 ---------------------- .../Marketplace/resources/qml/ManagedPackages.qml | 4 ++-- plugins/Marketplace/resources/qml/Materials.qml | 5 ++-- plugins/Marketplace/resources/qml/Plugins.qml | 5 ++-- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6cf2593bbf..21924a2680 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -2015,33 +2015,6 @@ class CuraApplication(QtApplication): show_whatsnew_only = has_active_machine and has_app_just_upgraded return show_whatsnew_only - @pyqtSlot(result = bool) - def shouldShowMarketPlacePluginBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_plugin_banner") - - @pyqtSlot(result = bool) - def shouldShowMarketPlaceMaterialBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_material_banner") - - @pyqtSlot(result = bool) - def shouldShowMarketPlaceManagePackagesBanner(self) -> bool: - return self._preferences.getValue("cura/market_place_show_manage_packages_banner") - - @pyqtSlot() - def closeMarketPlacePluginBanner(self) -> None: - Logger.log("i", "Close market place plugin banner") - self._preferences.setValue("cura/market_place_show_plugin_banner", False) - - @pyqtSlot() - def closeMarketPlaceMaterialBanner(self) -> None: - Logger.log("i", "Close market place material banner") - self._preferences.setValue("cura/market_place_show_material_banner", False) - - @pyqtSlot() - def closeMarketPlaceManagePackagesBanner(self) -> None: - Logger.log("i", "Close market place manage packages banner") - self._preferences.setValue("cura/market_place_show_manage_packages_banner", False) - @pyqtSlot(result = int) def appWidth(self) -> int: main_window = QtApplication.getInstance().getMainWindow() diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index e8e54d77b5..677a9ee574 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -11,12 +11,12 @@ Packages { pageTitle: catalog.i18nc("@header", "Manage packages") - bannerVisible: CuraApplication.shouldShowMarketPlaceManagePackagesBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: "ArrowDoubleCircleRight" bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlaceManagePackagesBanner(); + UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index fc4869dac5..e4cd554334 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -2,17 +2,18 @@ // Cura is released under the terms of the LGPLv3 or higher. import Marketplace 1.0 as Marketplace +import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Install Materials") - bannerVisible: CuraApplication.shouldShowMarketPlaceMaterialBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: "Spool" bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlaceMaterialBanner(); + UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index f0c101153c..11aabedb85 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -2,17 +2,18 @@ // Cura is released under the terms of the LGPLv3 or higher. import Marketplace 1.0 as Marketplace +import UM 1.4 as UM Packages { pageTitle: catalog.i18nc("@header", "Install Plugins") - bannerVisible: CuraApplication.shouldShowMarketPlacePluginBanner() + bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: "Shop" bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { - CuraApplication.closeMarketPlacePluginBanner(); + UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } -- cgit v1.2.3 From f7720d2a0d6870a512b1957c3bbe4d26b9156d40 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:53:35 +0100 Subject: Rename property `bannerBody` to `bannerText` CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 677a9ee574..0d1472bb6a 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -13,7 +13,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: "ArrowDoubleCircleRight" - bannerBody: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") + bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index e4cd554334..a1d6d91f8c 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: "Spool" - bannerBody: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 4b5e494be5..8d68512878 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -13,7 +13,7 @@ Rectangle { property bool bannerVisible property string bannerIcon - property string bannerBody + property string bannerText property var onRemoveBanner property string readMoreUrl @@ -82,7 +82,7 @@ Rectangle } font: UM.Theme.getFont("default") - text: bannerBody + text: bannerText renderType: Text.NativeRendering color: "white" diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 11aabedb85..884529c3e0 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: "Shop" - bannerBody: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) -- cgit v1.2.3 From 5dc664e8214e041ebbd86c9f1f43a8c30a14e8ba Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 11:55:33 +0100 Subject: Use `visible` property of `Rectangle` rather than defining a separate property CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 8d68512878..4a65297816 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,14 +11,11 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property bool bannerVisible property string bannerIcon property string bannerText property var onRemoveBanner property string readMoreUrl - visible: bannerVisible - Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height anchors { -- cgit v1.2.3 From 7e486578723b50ba7866492ddf0a1b4ebd9f2191 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:01:42 +0100 Subject: Use the `Item` element instead of `Rectangle` for banner icon To prevent un-necessary draw checks as the background is transparent CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 4a65297816..982bc0f501 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -28,7 +28,7 @@ Rectangle color: UM.Theme.getColor("action_panel_secondary") // Icon - Rectangle + Item { id: onboardingIcon anchors @@ -39,7 +39,6 @@ Rectangle } width: UM.Theme.getSize("button_icon").width height: UM.Theme.getSize("button_icon").height - color: "transparent" UM.RecolorImage { anchors.fill: parent -- cgit v1.2.3 From 67fc514b8fbc538e4c9b7f0b9888fe507c0828f6 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:16:52 +0100 Subject: Use an `alias` property for the banner icon CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 10 ++-------- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 0d1472bb6a..9ce8408f8e 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -12,7 +12,7 @@ Packages pageTitle: catalog.i18nc("@header", "Manage packages") bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); - bannerIcon: "ArrowDoubleCircleRight" + bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight") bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index a1d6d91f8c..de075af031 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -9,7 +9,7 @@ Packages pageTitle: catalog.i18nc("@header", "Install Materials") bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") - bannerIcon: "Spool" + bannerIcon: UM.Theme.getIcon("Spool") bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 982bc0f501..b44295c90a 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,7 +11,7 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property string bannerIcon + property alias bannerIcon: onboardingIcon.source; property string bannerText property var onRemoveBanner property string readMoreUrl @@ -28,7 +28,7 @@ Rectangle color: UM.Theme.getColor("action_panel_secondary") // Icon - Item + UM.RecolorImage { id: onboardingIcon anchors @@ -39,12 +39,6 @@ Rectangle } width: UM.Theme.getSize("button_icon").width height: UM.Theme.getSize("button_icon").height - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary_text") - source: UM.Theme.getIcon(bannerIcon) - } } // Close button diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 884529c3e0..2922a39c9b 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -9,7 +9,7 @@ Packages pageTitle: catalog.i18nc("@header", "Install Plugins") bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") - bannerIcon: "Shop" + bannerIcon: UM.Theme.getIcon("Shop") bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") readMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { -- cgit v1.2.3 From ad1798ee6e6996aa75a2ed22130afdb7f5354c24 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:26:48 +0100 Subject: Use color from theme for the market place banner banner text CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index b44295c90a..148a279cd2 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -75,7 +75,7 @@ Rectangle text: bannerText renderType: Text.NativeRendering - color: "white" + color: UM.Theme.getColor("primary_text") wrapMode: Text.Wrap elide: Text.ElideRight -- cgit v1.2.3 From 8d8de1c6f486506d1257c2337195e678c8a0956e Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 12:34:25 +0100 Subject: Move static anchor properties inside the `TertiaryButton` element CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 148a279cd2..62f6b96356 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -87,16 +87,12 @@ Rectangle if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) { // If it does place it after the body text - readMoreButton.anchors.left = infoText.left; - readMoreButton.anchors.bottom = infoText.bottom; readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); readMoreButton.anchors.leftMargin = 0; } else { // Otherwise place it under the text - readMoreButton.anchors.left = infoText.left; - readMoreButton.anchors.bottom = infoText.bottom; readMoreButton.anchors.leftMargin = line.implicitWidth + UM.Theme.getSize("default_margin").width; readMoreButton.anchors.bottomMargin = 0; } @@ -114,6 +110,8 @@ Rectangle { visible: readMoreUrl !== "" id: readMoreButton + anchors.left: infoText.left + anchors.bottom: infoText.bottom text: "Learn More" textFont: UM.Theme.getFont("default") textColor: infoText.color -- cgit v1.2.3 From f10eea4fc4329677d5587478989ac209b7af4001 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 14:34:15 +0100 Subject: Remove top anchor from an element used in ColumnLayout This element did need additional margins at the top. Added margins to the ColumnLayout element instead. CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 62f6b96356..0107d6d816 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -22,7 +22,6 @@ Rectangle margins: UM.Theme.getSize("default_margin").width left: parent.left right: parent.right - top: parent.top } color: UM.Theme.getColor("action_panel_secondary") -- cgit v1.2.3 From fe19587b7f657ba88efcb11ff0a748edca5a6608 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 29 Nov 2021 15:09:33 +0100 Subject: Rename properties in marketplace onboarding banner CURA-8564 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 9 ++++----- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 9ce8408f8e..2610f7cd9d 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -14,7 +14,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner"); bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight") bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index de075af031..2634f7b328 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -11,7 +11,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: UM.Theme.getIcon("Spool") bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 0107d6d816..0dbe2cb897 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -11,9 +11,9 @@ import Cura 1.6 as Cura // Onboarding banner. Rectangle { - property alias bannerIcon: onboardingIcon.source; - property string bannerText - property var onRemoveBanner + property alias icon: onboardingIcon.source + property alias text: infoText.text + property var onRemove property string readMoreUrl Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height @@ -56,7 +56,7 @@ Rectangle hoverColor: UM.Theme.getColor("primary_text_hover") iconSource: UM.Theme.getIcon("Cancel") - onClicked: onRemoveBanner() + onClicked: onRemove() } // Body @@ -71,7 +71,6 @@ Rectangle } font: UM.Theme.getFont("default") - text: bannerText renderType: Text.NativeRendering color: UM.Theme.getColor("primary_text") diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 2922a39c9b..29b264c702 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -11,7 +11,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: UM.Theme.getIcon("Shop") bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") - readMoreUrl: "" // TODO add when support page is ready + bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; -- cgit v1.2.3 From 61275ed9bc7b4247219b6e54fad2db2a0e8c58bb Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 10:51:43 +0100 Subject: Easy navigation to Cloud marketplace CURA-8563 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/Plugins.qml | 1 + 3 files changed, 3 insertions(+) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 2610f7cd9d..b90bffd723 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -19,6 +19,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" model: Marketplace.LocalPackageList { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 2634f7b328..3afe7b412a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -16,6 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials" model: Marketplace.RemotePackageList { diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 29b264c702..c473a3a48e 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -16,6 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" model: Marketplace.RemotePackageList { -- cgit v1.2.3 From 628be10e984887cb66c2207a34865020333e5a97 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Nov 2021 14:17:40 +0100 Subject: Add campaign links CURA-8563 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 2 +- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index b90bffd723..f44fbd0a9b 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -19,7 +19,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false); bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" model: Marketplace.LocalPackageList { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 3afe7b412a..489915aa10 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -16,7 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_material_banner", false); bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-materials-browser" model: Marketplace.RemotePackageList { diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index c473a3a48e..3b0b5d7c23 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -16,7 +16,7 @@ Packages UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) bannerVisible = false; } - searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins" + searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" model: Marketplace.RemotePackageList { -- cgit v1.2.3 From e83f8e4b8a4ea91533c7990ccbc4856b8f0450e8 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:08:09 +0100 Subject: Add correct text to material and plugins onboarding banners CURA-8564 --- plugins/Marketplace/resources/qml/Materials.qml | 2 +- plugins/Marketplace/resources/qml/Plugins.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 489915aa10..39d283b0a5 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner") bannerIcon: UM.Theme.getIcon("Spool") - bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") + bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_material_banner", false); diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 3b0b5d7c23..538afc827a 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -10,7 +10,7 @@ Packages bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner") bannerIcon: UM.Theme.getIcon("Shop") - bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.") + bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.") bannerReadMoreUrl: "" // TODO add when support page is ready onRemoveBanner: function() { UM.Preferences.setValue("cura/market_place_show_plugin_banner", false) -- cgit v1.2.3 From 2618ad4a0fe021f3796ebe8c194f70eb1117d46a Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:10:55 +0100 Subject: Always show read more button in on boarding banner Even if there is no link CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 0dbe2cb897..90af5f9b4f 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -106,7 +106,6 @@ Rectangle Cura.TertiaryButton { - visible: readMoreUrl !== "" id: readMoreButton anchors.left: infoText.left anchors.bottom: infoText.bottom -- cgit v1.2.3 From b07b5d5bc4826bd4c7a1571e6c64bf30ed5f2885 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:11:32 +0100 Subject: Change margins of read more button in marketplace onboarding banner To comply with UX design CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index 90af5f9b4f..a2c1613bcb 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -85,8 +85,8 @@ Rectangle if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width) { // If it does place it after the body text - readMoreButton.anchors.bottomMargin = -(fontMetrics.height + UM.Theme.getSize("thin_margin").height); - readMoreButton.anchors.leftMargin = 0; + readMoreButton.anchors.bottomMargin = -(fontMetrics.height); + readMoreButton.anchors.leftMargin = UM.Theme.getSize("thin_margin").width; } else { -- cgit v1.2.3 From 52c8e6b21700aa7da819461af2d4d6d2a9ff5a38 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 30 Nov 2021 12:19:32 +0100 Subject: Decrease size of the icons in the marketplace onboarding banners CURA-8564 --- plugins/Marketplace/resources/qml/OnboardBanner.qml | 4 ++-- resources/themes/cura-light/theme.json | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index a2c1613bcb..f77f8bee97 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -36,8 +36,8 @@ Rectangle left: parent.left margins: UM.Theme.getSize("default_margin").width } - width: UM.Theme.getSize("button_icon").width - height: UM.Theme.getSize("button_icon").height + width: UM.Theme.getSize("banner_icon_size").width + height: UM.Theme.getSize("banner_icon_size").height } // Close button diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 6dec15aff8..8ca9f72ca8 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -684,6 +684,8 @@ "table_row": [2.0, 2.0], "welcome_wizard_content_image_big": [18, 15], - "welcome_wizard_cloud_content_image": [4, 4] + "welcome_wizard_cloud_content_image": [4, 4], + + "banner_icon_size": [2.0, 2.0] } } -- cgit v1.2.3 From df66bcb541f9edc1aff284239c912f3fc3d043d3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 30 Nov 2021 19:00:08 +0100 Subject: Add additional buttons at the bottom for materials with links to data sheets And where to buy it. Contributes to issue CURA-8585. --- resources/themes/cura-light/icons/default/DocumentFilled.svg | 3 +++ resources/themes/cura-light/icons/default/ShoppingCart.svg | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 resources/themes/cura-light/icons/default/DocumentFilled.svg create mode 100644 resources/themes/cura-light/icons/default/ShoppingCart.svg diff --git a/resources/themes/cura-light/icons/default/DocumentFilled.svg b/resources/themes/cura-light/icons/default/DocumentFilled.svg new file mode 100644 index 0000000000..bb654fea33 --- /dev/null +++ b/resources/themes/cura-light/icons/default/DocumentFilled.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/themes/cura-light/icons/default/ShoppingCart.svg b/resources/themes/cura-light/icons/default/ShoppingCart.svg new file mode 100644 index 0000000000..b3fece3fab --- /dev/null +++ b/resources/themes/cura-light/icons/default/ShoppingCart.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From c1cffa09fe51b61de3024d2b0511f97084b0f89e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 1 Dec 2021 13:22:50 +0100 Subject: Solve layout warnings. --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- plugins/Marketplace/resources/qml/OnboardBanner.qml | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index a02c0e0d5f..bee825d955 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -117,7 +117,7 @@ Window TabBar { id: pageSelectionTabBar - anchors.right: parent.right + Layout.alignment: Qt.AlignRight height: UM.Theme.getSize("button_icon").height spacing: 0 background: Rectangle { color: "transparent" } diff --git a/plugins/Marketplace/resources/qml/OnboardBanner.qml b/plugins/Marketplace/resources/qml/OnboardBanner.qml index f77f8bee97..25e4b53241 100644 --- a/plugins/Marketplace/resources/qml/OnboardBanner.qml +++ b/plugins/Marketplace/resources/qml/OnboardBanner.qml @@ -17,12 +17,8 @@ Rectangle property string readMoreUrl Layout.preferredHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height - anchors - { - margins: UM.Theme.getSize("default_margin").width - left: parent.left - right: parent.right - } + Layout.fillWidth: true + Layout.margins: UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("action_panel_secondary") -- cgit v1.2.3 From 327b434788e246a7783704bb73cca27ddca9da51 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 1 Dec 2021 18:32:14 +0100 Subject: fix merge conflict Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 3 ++- plugins/Marketplace/resources/qml/ManageButton.qml | 4 ++++ plugins/Marketplace/resources/qml/Packages.qml | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 798a15324e..9c21c03745 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -7,7 +7,8 @@ from typing import Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. -from UM.TaskManagement.HttpRequestManager import HttpRequestData # To request the package list from the API. +from UM.TaskManagement.HttpRequestManager import HttpRequestData # To request the package list from the API +from UM.Logger import Logger from cura.CuraApplication import CuraApplication from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index e58347124a..889a8ce7f3 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -17,6 +17,8 @@ RowLayout property string busySecondaryText: busyMessageText.text property string mainState: "primary" + signal clicked + state: mainState Cura.PrimaryButton @@ -26,6 +28,7 @@ RowLayout onClicked: { + manageButton.clicked() manageButton.state = "busy" } } @@ -37,6 +40,7 @@ RowLayout onClicked: { + manageButton.clicked() manageButton.state = "busy" } } diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 3c627aef32..275792ed42 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -38,7 +38,6 @@ ListView text: section font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") - onTextChanged: print(text) } } -- cgit v1.2.3 From ff5a4a4f5afbd15a187a50916015dfdcff845d25 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 2 Dec 2021 08:21:57 +0100 Subject: Adding functionality to the manageButtons Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 4 +++- plugins/Marketplace/PackageList.py | 27 ++++++++++++++++++++++ plugins/Marketplace/RemotePackageList.py | 1 + plugins/Marketplace/resources/qml/ManageButton.qml | 6 ++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index bbe74a9056..be805fb002 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -60,7 +60,9 @@ class LocalPackageList(PackageList): bundled_or_installed = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" package_type = package_info["package_type"] section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type] - return PackageModel(package_info, section_title = section_title, parent = self) + package = PackageModel(package_info, section_title = section_title, parent = self) + self._connectManageButtonSignals(package) + return package def checkForUpdates(self, packages: List[Dict[str, Any]]): installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 9c21c03745..1ce8d3fe1d 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -101,3 +101,30 @@ class PackageList(ListModel): """ Indicating if the PackageList should have a Footer visible. For paginated PackageLists :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise""" return self._has_footer + + def _connectManageButtonSignals(self, package): + package.installPackageTriggered.connect(self.installPackage) + package.uninstallPackageTriggered.connect(self.uninstallPackage) + package.updatePackageTriggered.connect(self.updatePackage) + package.enablePackageTriggered.connect(self.enablePackage) + package.disablePackageTriggered.connect(self.disablePackage) + + @pyqtSlot(str) + def installPackage(self, package_id): + Logger.debug(f"Installing {package_id}") + + @pyqtSlot(str) + def uninstallPackage(self, package_id): + Logger.debug(f"Uninstalling {package_id}") + + @pyqtSlot(str) + def updatePackage(self, package_id): + Logger.debug(f"Updating {package_id}") + + @pyqtSlot(str) + def enablePackage(self, package_id): + Logger.debug(f"Enabling {package_id}") + + @pyqtSlot(str) + def disablePackage(self, package_id): + Logger.debug(f"Disabling {package_id}") diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index d5c0763609..63370042e7 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -133,6 +133,7 @@ class RemotePackageList(PackageList): continue # We should only show packages which are not already installed try: package = PackageModel(package_data, parent = self) + self._connectManageButtonSignals(package) 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 889a8ce7f3..b13a31c5cf 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -17,7 +17,7 @@ RowLayout property string busySecondaryText: busyMessageText.text property string mainState: "primary" - signal clicked + signal clicked(bool primary_action) state: mainState @@ -28,7 +28,7 @@ RowLayout onClicked: { - manageButton.clicked() + manageButton.clicked(true) manageButton.state = "busy" } } @@ -40,7 +40,7 @@ RowLayout onClicked: { - manageButton.clicked() + manageButton.clicked(false) manageButton.state = "busy" } } -- cgit v1.2.3 From 08067432c6115a669eafbca7088ba2eaf6fa4cae Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 2 Dec 2021 08:54:40 +0100 Subject: disable other manageButtons when actions is performed Still need to make this transfer to the detaile card Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index b13a31c5cf..035c369fd8 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -16,6 +16,8 @@ RowLayout property string busyPrimaryText: busyMessageText.text property string busySecondaryText: busyMessageText.text property string mainState: "primary" + property bool enabled: true + readonly property bool busy: state == "busy" signal clicked(bool primary_action) @@ -25,6 +27,7 @@ RowLayout { id: primaryButton visible: false + enabled: manageButton.enabled onClicked: { @@ -37,6 +40,7 @@ RowLayout { id: secondaryButton visible: false + enabled: manageButton.enabled onClicked: { -- cgit v1.2.3 From 3b3d9860581860939b4ef257e44045ef15c2ad27 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 2 Dec 2021 18:02:49 +0100 Subject: Groundwork for installing/updating packages Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 6 +- plugins/Marketplace/LocalPackageList.py | 5 +- plugins/Marketplace/PackageList.py | 68 ++++++++++++++++++++-- plugins/Marketplace/resources/qml/ManageButton.qml | 8 +-- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index a8400bfae7..34d8c5c61f 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -20,12 +20,16 @@ class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) self._locally_installed_packages = None + self.installedPackagesChanged.connect(self._updateLocallyInstalledPackages) + + def _updateLocallyInstalledPackages(self): + self._locally_installed_packages = list(self.iterateAllLocalPackages()) @property def locally_installed_packages(self): """locally installed packages, lazy execution""" if self._locally_installed_packages is None: - self._locally_installed_packages = list(self.iterateAllLocalPackages()) + self._updateLocallyInstalledPackages() return self._locally_installed_packages @locally_installed_packages.setter diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index be805fb002..4721224e16 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -89,7 +89,8 @@ class LocalPackageList(PackageList): return for package_data in response_data["data"]: - index = self.find("package", package_data["package_id"]) - self.getItem(index)["package"].canUpdate = True + package = self._getPackageModel(package_data["package_id"]) + package.download_url = package_data.get("download_url", "") + package.canUpdate = True self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 1ce8d3fe1d..32eb302e97 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,18 +1,21 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import tempfile from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import Optional, TYPE_CHECKING +from typing import Dict, Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel -from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. -from UM.TaskManagement.HttpRequestManager import HttpRequestData # To request the package list from the API +from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope +from UM.TaskManagement.HttpRequestManager import HttpRequestData , HttpRequestManager from UM.Logger import Logger from cura.CuraApplication import CuraApplication from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. +from .PackageModel import PackageModel + if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -24,6 +27,7 @@ class PackageList(ListModel): such as Packages obtained from Remote or Local source """ PackageRole = Qt.UserRole + 1 + DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) @@ -33,6 +37,8 @@ class PackageList(ListModel): self._is_loading = False self._has_more = False self._has_footer = True + self._to_install: Dict[str, str] = {} + self.canInstallChanged.connect(self._install) self._ongoing_request: Optional[HttpRequestData] = None self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) @@ -105,13 +111,60 @@ class PackageList(ListModel): def _connectManageButtonSignals(self, package): package.installPackageTriggered.connect(self.installPackage) package.uninstallPackageTriggered.connect(self.uninstallPackage) - package.updatePackageTriggered.connect(self.updatePackage) + package.updatePackageTriggered.connect(self.installPackage) package.enablePackageTriggered.connect(self.enablePackage) package.disablePackageTriggered.connect(self.disablePackage) + def _getPackageModel(self, package_id: str) -> PackageModel: + index = self.find("package", package_id) + return self.getItem(index)["package"] + + canInstallChanged = pyqtSignal(str, bool) + + def download(self, package_id, url, update: bool = False): + + def downloadFinished(reply: "QNetworkReply") -> None: + self._downloadFinished(package_id, reply, update) + + HttpRequestManager.getInstance().get( + url, + scope = self._scope, + callback = downloadFinished + ) + + def _downloadFinished(self, package_id: str, reply: "QNetworkReply", update: bool = False) -> None: + try: + with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file: + bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) + while bytes_read: + temp_file.write(bytes_read) + bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) + Logger.debug(f"Finished downloading {package_id} and stored it as {temp_file.name}") + self._to_install[package_id] = temp_file.name + self.canInstallChanged.emit(package_id, update) + except IOError as e: + Logger.logException("e", "Failed to write downloaded package to temp file", e) + temp_file.close() + @pyqtSlot(str) - def installPackage(self, package_id): + def installPackage(self, package_id: str) -> None: + package = self._getPackageModel(package_id) + url = package.download_url + Logger.debug(f"Trying to download and install {package_id} from {url}") + self.download(package_id, url) + + def _install(self, package_id: str, update: bool = False) -> None: + package_path = self._to_install.pop(package_id) Logger.debug(f"Installing {package_id}") + to_be_installed = self._manager.installPackage(package_path) != None + package = self._getPackageModel(package_id) + if package.canUpdate and to_be_installed: + package.canUpdate = False + package.setManageInstallState(to_be_installed) + if update: + package.setIsUpdating(False) + else: + package.setIsInstalling(False) @pyqtSlot(str) def uninstallPackage(self, package_id): @@ -119,7 +172,10 @@ class PackageList(ListModel): @pyqtSlot(str) def updatePackage(self, package_id): - Logger.debug(f"Updating {package_id}") + package = self._getPackageModel(package_id) + url = package.download_url + Logger.debug(f"Trying to download and update {package_id} from {url}") + self.download(package_id, url, True) @pyqtSlot(str) def enablePackage(self, package_id): diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 035c369fd8..797e83830f 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -17,11 +17,11 @@ RowLayout property string busySecondaryText: busyMessageText.text property string mainState: "primary" property bool enabled: true - readonly property bool busy: state == "busy" + property bool busy: false signal clicked(bool primary_action) - state: mainState + state: busy ? "busy" : mainState Cura.PrimaryButton { @@ -32,7 +32,6 @@ RowLayout onClicked: { manageButton.clicked(true) - manageButton.state = "busy" } } @@ -45,7 +44,6 @@ RowLayout onClicked: { manageButton.clicked(false) - manageButton.state = "busy" } } @@ -155,7 +153,7 @@ RowLayout PropertyChanges { target: busyMessage - visible: true + visible: manageButton.visible } } ] -- cgit v1.2.3 From 00acfe9d72dcd77af586a8abe3c16c83f572c5e6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 3 Dec 2021 11:15:04 +0100 Subject: Added uninstall functionality Get it in a sharable state Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 1 - plugins/Marketplace/PackageList.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 34d8c5c61f..e6123c1947 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -69,7 +69,6 @@ class CuraPackageManager(PackageManager): def iterateAllLocalPackages(self) -> Generator[Dict[str, Any], None, None]: """ A generator which returns an unordered list of all the PackageModels""" - # Get all the installed packages, add a section_title depending on package_type and user installed for packages in self.getAllInstalledPackagesInfo().values(): for package_info in packages: yield package_info diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 32eb302e97..d988ce94bf 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -12,6 +12,8 @@ from UM.TaskManagement.HttpRequestManager import HttpRequestData , HttpRequestMa from UM.Logger import Logger from cura.CuraApplication import CuraApplication +from cura.API.Account import Account +from cura import CuraPackageManager from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from .PackageModel import PackageModel @@ -31,7 +33,8 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._manager = CuraApplication.getInstance().getPackageManager() + self._manager: CuraPackageManager = CuraApplication.getInstance().getPackageManager() + self._account: Account = CuraApplication.getInstance().getCuraAPI().account self._error_message = "" self.addRoleName(self.PackageRole, "package") self._is_loading = False @@ -126,7 +129,7 @@ class PackageList(ListModel): def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) - HttpRequestManager.getInstance().get( + self._ongoing_request = HttpRequestManager.getInstance().get( url, scope = self._scope, callback = downloadFinished @@ -165,13 +168,34 @@ class PackageList(ListModel): package.setIsUpdating(False) else: package.setIsInstalling(False) + #self._subscribe(package_id) + + def _subscribe(self, package_id: str) -> None: + if self._account.isLoggedIn: + Logger.debug(f"Subscribing the user for package: {package_id}") + self._ongoing_request = HttpRequestManager.getInstance().put( + url = "", + data = {}, + scope = self._scope + ) @pyqtSlot(str) def uninstallPackage(self, package_id): Logger.debug(f"Uninstalling {package_id}") + package = self._getPackageModel(package_id) + self._manager.removePackage(package_id) + package.setIsInstalling(False) + package.setManageInstallState(False) + #self._unsunscribe(package_id) + + def _unsunscribe(self, package_id: str) -> None: + if self._account.isLoggedIn: + Logger.debug(f"Unsubscribing the user for package: {package_id}") + self._ongoing_request = HttpRequestManager.getInstance().delete(url = "", scope = self._scope) @pyqtSlot(str) def updatePackage(self, package_id): + self._manager.removePackage(package_id, force_add = True) package = self._getPackageModel(package_id) url = package.download_url Logger.debug(f"Trying to download and update {package_id} from {url}") -- cgit v1.2.3 From e37f08790ff77674753c4631f6d4807c5a846d6f Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 29 Nov 2021 12:32:00 +0100 Subject: Fixed a merge conflict And not in a neat way. I'm a shamed to say copy-paste was used extensively. Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 77 ++++++++++++++++++---- plugins/Marketplace/resources/qml/PackageCard.qml | 78 +++++++++++++++++++---- 2 files changed, 130 insertions(+), 25 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index d123550c28..c9164bbe26 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal import re -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.Logger import Logger @@ -20,11 +20,10 @@ class PackageModel(QObject): QML. The model can also be constructed directly from a response received by the API. """ - def __init__(self, package_data: Dict[str, Any], installation_status: str, section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: + def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: """ Constructs a new model for a single package. :param package_data: The data received from the Marketplace API about the package to create. - :param installation_status: Whether the package is `not_installed`, `installed` or `bundled`. :param section_title: If the packages are to be categorized per section provide the section_title :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ @@ -44,7 +43,7 @@ class PackageModel(QObject): self._description = package_data.get("description", "") self._formatted_description = self._format(self._description) - self._download_url = package_data.get("download_url", "") + self.download_url = package_data.get("download_url", "") self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? subdata = package_data.get("data", {}) @@ -62,10 +61,21 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") - self._installation_status = installation_status + self._can_update = False + self._is_installing = False + self._is_updating = False self._section_title = section_title # Note that there's a lot more info in the package_data than just these specified here. + def __eq__(self, other: Union[str, "PackageModel"]): + if isinstance(other, PackageModel): + return other == self + else: + return other == self._package_id + + def __repr__(self): + return f"<{self._package_id} : {self._package_version} : {self._section_title}>" + def _findLink(self, subdata: Dict[str, Any], link_type: str) -> str: """ Searches the package data for a link of a certain type. @@ -219,10 +229,6 @@ class PackageModel(QObject): def authorInfoUrl(self): return self._author_info_url - @pyqtProperty(str, constant = True) - def installationStatus(self) -> str: - return self._installation_status - @pyqtProperty(str, constant = True) def sectionTitle(self) -> Optional[str]: return self._section_title @@ -255,6 +261,28 @@ class PackageModel(QObject): def isCompatibleAirManager(self) -> bool: return self._is_compatible_air_manager + isInstallingChanged = pyqtSignal() + + def setIsInstalling(self, value: bool) -> None: + if value != self._is_installing: + self._is_installing = value + self.isInstallingChanged.emit() + + @pyqtProperty(bool, fset = setIsInstalling, notify = isInstallingChanged) + def isInstalling(self) -> bool: + return self._is_installing + + isUpdatingChanged = pyqtSignal() + + def setIsUpdating(self, value: bool) -> None: + if value != self._is_updating: + self._is_updating = value + self.isUpdatingChanged.emit() + + @pyqtProperty(bool, fset = setIsUpdating, notify = isUpdatingChanged) + def isUpdating(self) -> bool: + return self._is_updating + isInstalledChanged = pyqtSignal() @pyqtProperty(bool, notify = isInstalledChanged) @@ -282,8 +310,13 @@ class PackageModel(QObject): manageInstallStateChanged = pyqtSignal() + def setManageInstallState(self, value: bool) -> None: + if value != self._is_installed: + self._is_installed = value + self.manageInstallStateChanged.emit() + @pyqtProperty(str, notify = manageInstallStateChanged) - def manageInstallState(self): + def manageInstallState(self) -> str: if self._is_installed: if self._is_bundled: return "hidden" @@ -296,4 +329,26 @@ class PackageModel(QObject): @pyqtProperty(str, notify = manageUpdateStateChanged) def manageUpdateState(self): - return "hidden" # TODO: implement + if self._can_update: + return "primary" + return "hidden" + + @property + def canUpdate(self): + return self._can_update + + @canUpdate.setter + def canUpdate(self, value): + if value != self._can_update: + self._can_update = value + self.manageUpdateStateChanged.emit() + + installPackageTriggered = pyqtSignal(str) + + uninstallPackageTriggered = pyqtSignal(str) + + updatePackageTriggered = pyqtSignal(str) + + enablePackageTriggered = pyqtSignal(str) + + disablePackageTriggered = pyqtSignal(str) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index b8f815bedf..ad49c650e8 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -302,7 +302,6 @@ Rectangle width: UM.Theme.getSize("card_tiny_icon").width height: UM.Theme.getSize("card_tiny_icon").height - visible: packageData.installationStatus !== "bundled" //Don't show download count for packages that are bundled. It'll usually be 0. source: UM.Theme.getIcon("Download") color: UM.Theme.getColor("text") } @@ -311,7 +310,6 @@ Rectangle { anchors.verticalCenter: downloadsIcon.verticalCenter - visible: packageData.installationStatus !== "bundled" //Don't show download count for packages that are bundled. It'll usually be 0. color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") text: packageData.downloadCount @@ -354,28 +352,80 @@ Rectangle onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) } - Cura.SecondaryButton + ManageButton { - id: disableButton + id: enableManageButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Disable") - visible: false // not functional right now, also only when unfolding and required + primaryText: catalog.i18nc("@button", "Enable") + busyPrimaryText: catalog.i18nc("@button", "enabling...") + secondaryText: catalog.i18nc("@button", "Disable") + busySecondaryText: catalog.i18nc("@button", "disabling...") + mainState: packageData.manageEnableState + enabled: !(installManageButton.busy || updateManageButton.busy) + } + Connections + { + target: enableManageButton + function onClicked(primary_action) + { + if (primary_action) + { + packageData.enablePackageTriggered(packageData.packageId) + } + else + { + packageData.disablePackageTriggered(packageData.packageId) + } + } } - Cura.SecondaryButton + ManageButton { - id: uninstallButton + id: installManageButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Uninstall") - visible: false // not functional right now, also only when unfolding and required + primaryText: catalog.i18nc("@button", "Install") + busyPrimaryText: catalog.i18nc("@button", "installing...") + secondaryText: catalog.i18nc("@button", "Uninstall") + busySecondaryText: catalog.i18nc("@button", "uninstalling...") + mainState: packageData.manageInstallState + busy: packageData.isInstalling + enabled: !(enableManageButton.busy || updateManageButton.busy) + } + Connections + { + target: installManageButton + function onClicked(primary_action) + { + packageData.isInstalling = true + if (primary_action) + { + packageData.installPackageTriggered(packageData.packageId) + } + else + { + packageData.uninstallPackageTriggered(packageData.packageId) + } + } } - Cura.PrimaryButton + ManageButton { - id: installButton + id: updateManageButton Layout.alignment: Qt.AlignTop - text: catalog.i18nc("@button", "Update") // OR Download, if new! - visible: false // not functional right now, also only when unfolding and required + primaryText: catalog.i18nc("@button", "Update") + busyPrimaryText: catalog.i18nc("@button", "updating...") + mainState: packageData.manageUpdateState + busy: packageData.isUpdating + enabled: !(installManageButton.busy || enableManageButton.busy) + } + Connections + { + target: updateManageButton + function onClicked(primary_action) + { + packageData.isUpdating = true + packageData.updatePackageTriggered(packageData.packageId) + } } } } -- cgit v1.2.3 From a83a598e96467d3a0258de71f136ccbc481cc2a0 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 3 Dec 2021 15:50:51 +0100 Subject: Added error handling when downloading packages failed A simple callback function which ensures that the proper signals are emitted when we fail to retrieve the package. Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index d988ce94bf..2a1283a5ba 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -129,10 +129,14 @@ class PackageList(ListModel): def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) - self._ongoing_request = HttpRequestManager.getInstance().get( + def downloadError(reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None: + self._downloadError(package_id, update, reply, error) + + HttpRequestManager.getInstance().get( url, scope = self._scope, - callback = downloadFinished + callback = downloadFinished, + error_callback = downloadError ) def _downloadFinished(self, package_id: str, reply: "QNetworkReply", update: bool = False) -> None: @@ -146,8 +150,19 @@ class PackageList(ListModel): self._to_install[package_id] = temp_file.name self.canInstallChanged.emit(package_id, update) except IOError as e: - Logger.logException("e", "Failed to write downloaded package to temp file", e) + Logger.error(f"Failed to write downloaded package to temp file {e}") temp_file.close() + self._downloadError(package_id, update) + + def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None: + if reply: + reply_string = bytes(reply.readAll()).decode() + Logger.error(f"Failed to download package: {package_id} due to {reply_string}") + package = self._getPackageModel(package_id) + if update: + package.setIsUpdating(False) + else: + package.setIsInstalling(False) @pyqtSlot(str) def installPackage(self, package_id: str) -> None: @@ -168,7 +183,7 @@ class PackageList(ListModel): package.setIsUpdating(False) else: package.setIsInstalling(False) - #self._subscribe(package_id) + # self._subscribe(package_id) def _subscribe(self, package_id: str) -> None: if self._account.isLoggedIn: -- cgit v1.2.3 From 743ac67cdb27559d779703510d97839552d11a79 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 3 Dec 2021 17:08:28 +0100 Subject: un-/subscribe the user to installed packages Contributes to: CURA-8587 --- plugins/Marketplace/Constants.py | 11 +++++++++++ plugins/Marketplace/LocalPackageList.py | 4 ++-- plugins/Marketplace/Marketplace.py | 6 ------ plugins/Marketplace/PackageList.py | 16 +++++++++------- plugins/Marketplace/PackageModel.py | 1 + plugins/Marketplace/RemotePackageList.py | 4 ++-- 6 files changed, 25 insertions(+), 17 deletions(-) create mode 100644 plugins/Marketplace/Constants.py diff --git a/plugins/Marketplace/Constants.py b/plugins/Marketplace/Constants.py new file mode 100644 index 0000000000..bc6d1f05fa --- /dev/null +++ b/plugins/Marketplace/Constants.py @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from cura.UltimakerCloud import UltimakerCloudConstants +from cura.ApplicationMetadata import CuraSDKVersion + +ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}" +ROOT_CURA_URL = f"{ROOT_URL}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. +ROOT_USER_URL = f"{ROOT_URL}/user" +PACKAGES_URL = f"{ROOT_CURA_URL}/packages" # URL to use for requesting the list of packages. +PACKAGE_UPDATES_URL = f"{PACKAGES_URL}/package-updates" # URL to use for requesting the list of packages that can be updated. +USER_PACKAGES_URL = f"{ROOT_USER_URL}/packages" diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 4721224e16..284f51c806 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -15,7 +15,7 @@ from UM.Logger import Logger from .PackageList import PackageList from .PackageModel import PackageModel -from . import Marketplace +from .Constants import PACKAGE_UPDATES_URL catalog = i18nCatalog("cura") @@ -66,7 +66,7 @@ class LocalPackageList(PackageList): def checkForUpdates(self, packages: List[Dict[str, Any]]): installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) - request_url = f"{Marketplace.PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" + request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" self._ongoing_request = HttpRequestManager.getInstance().get( request_url, diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 1b98503969..89ad986920 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -6,9 +6,7 @@ from PyQt5.QtCore import pyqtSlot from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING -from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. -from cura.UltimakerCloud import UltimakerCloudConstants from UM.Extension import Extension # We are implementing the main object of an extension here. from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). @@ -19,10 +17,6 @@ from .LocalPackageList import LocalPackageList # To register this type with QML if TYPE_CHECKING: from PyQt5.QtCore import QObject -ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. -PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. -PACKAGE_UPDATES_URL = f"{PACKAGES_URL}/package-updates" # URL to use for requesting the list of packages that can be updated. - class Marketplace(Extension): """ diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 2a1283a5ba..3c86040a00 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,6 +1,7 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import tempfile +import json from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt from typing import Dict, Optional, TYPE_CHECKING @@ -17,6 +18,7 @@ from cura import CuraPackageManager from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from .PackageModel import PackageModel +from .Constants import USER_PACKAGES_URL if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -183,14 +185,14 @@ class PackageList(ListModel): package.setIsUpdating(False) else: package.setIsInstalling(False) - # self._subscribe(package_id) + self._subscribe(package_id, str(package.sdk_version)) - def _subscribe(self, package_id: str) -> None: + def _subscribe(self, package_id: str, sdk_version: str) -> None: if self._account.isLoggedIn: Logger.debug(f"Subscribing the user for package: {package_id}") - self._ongoing_request = HttpRequestManager.getInstance().put( - url = "", - data = {}, + HttpRequestManager.getInstance().put( + url = USER_PACKAGES_URL, + data = json.dumps({"data": {"package_id": package_id, "sdk_version": sdk_version}}).encode(), scope = self._scope ) @@ -201,12 +203,12 @@ class PackageList(ListModel): self._manager.removePackage(package_id) package.setIsInstalling(False) package.setManageInstallState(False) - #self._unsunscribe(package_id) + self._unsunscribe(package_id) def _unsunscribe(self, package_id: str) -> None: if self._account.isLoggedIn: Logger.debug(f"Unsubscribing the user for package: {package_id}") - self._ongoing_request = HttpRequestManager.getInstance().delete(url = "", scope = self._scope) + HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) @pyqtSlot(str) def updatePackage(self, package_id): diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index c9164bbe26..e6615283d9 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -65,6 +65,7 @@ class PackageModel(QObject): self._is_installing = False self._is_updating = 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 __eq__(self, other: Union[str, "PackageModel"]): diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 63370042e7..a8fb20e88f 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -9,7 +9,7 @@ from UM.i18n import i18nCatalog from UM.Logger import Logger from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. -from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. +from .Constants import PACKAGES_URL # To get the list of packages. Imported this way to prevent circular imports. from .PackageList import PackageList from .PackageModel import PackageModel # The contents of this list. @@ -108,7 +108,7 @@ class RemotePackageList(PackageList): Get the URL to request the first paginated page with. :return: A URL to request. """ - request_url = f"{Marketplace.PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" + request_url = f"{PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" if self._package_type_filter != "": request_url += f"&package_type={self._package_type_filter}" if self._current_search_string != "": -- cgit v1.2.3 From 4c570c87e9388d9d7b01c6bf70da448aa73a6d2c Mon Sep 17 00:00:00 2001 From: casper Date: Fri, 3 Dec 2021 17:19:18 +0100 Subject: Make sure recently installed packages only appear once in package list Some packages might be added to both `getPackagesToInstall` and `getAllInstalledPackagesInfo` cura 8587 --- cura/CuraPackageManager.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index e6123c1947..6c62442758 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -68,16 +68,24 @@ class CuraPackageManager(PackageManager): def iterateAllLocalPackages(self) -> Generator[Dict[str, Any], None, None]: """ A generator which returns an unordered list of all the PackageModels""" + handled_packages = set() for packages in self.getAllInstalledPackagesInfo().values(): for package_info in packages: - yield package_info + if not handled_packages.__contains__(package_info["package_id"]): + handled_packages.add(package_info["package_id"]) + yield package_info # Get all to be removed package_info's. These packages are still used in the current session so the user might # still want to interact with these. for package_data in self.getPackagesToRemove().values(): - yield package_data["package_info"] + for package_data in self.getPackagesToRemove().values(): + if not handled_packages.__contains__(package_data["package_info"]["package_id"]): + handled_packages.add(package_data["package_info"]["package_id"]) + yield package_data["package_info"] # Get all to be installed package_info's. Since the user might want to interact with these for package_data in self.getPackagesToInstall().values(): - yield package_data["package_info"] + if not handled_packages.__contains__(package_data["package_info"]["package_id"]): + handled_packages.add(package_data["package_info"]["package_id"]) + yield package_data["package_info"] \ No newline at end of file -- cgit v1.2.3 From c937337324427c3c3b2d897ceea9caf838271c0e Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 3 Dec 2021 17:21:38 +0100 Subject: Change the state of the enable button after an un-/install Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index e6615283d9..3f602d1384 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -315,6 +315,7 @@ class PackageModel(QObject): if value != self._is_installed: self._is_installed = value self.manageInstallStateChanged.emit() + self.manageEnableStateChanged.emit() @pyqtProperty(str, notify = manageInstallStateChanged) def manageInstallState(self) -> str: -- cgit v1.2.3 From 8400459e212214637247445260cf5eeda99ed131 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 3 Dec 2021 17:41:29 +0100 Subject: Use Python syntax to check if item is in collection Als removed the check on the first loop, because we know for certain these will already be unique values. Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 6c62442758..98e0e592aa 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -68,24 +68,22 @@ class CuraPackageManager(PackageManager): def iterateAllLocalPackages(self) -> Generator[Dict[str, Any], None, None]: """ A generator which returns an unordered list of all the PackageModels""" - handled_packages = set() + handled_packages = {} for packages in self.getAllInstalledPackagesInfo().values(): for package_info in packages: - if not handled_packages.__contains__(package_info["package_id"]): - handled_packages.add(package_info["package_id"]) - yield package_info + handled_packages.add(package_info["package_id"]) + yield package_info # Get all to be removed package_info's. These packages are still used in the current session so the user might # still want to interact with these. for package_data in self.getPackagesToRemove().values(): - for package_data in self.getPackagesToRemove().values(): - if not handled_packages.__contains__(package_data["package_info"]["package_id"]): + if not package_data["package_info"]["package_id"] in handled_packages: handled_packages.add(package_data["package_info"]["package_id"]) yield package_data["package_info"] # Get all to be installed package_info's. Since the user might want to interact with these for package_data in self.getPackagesToInstall().values(): - if not handled_packages.__contains__(package_data["package_info"]["package_id"]): + if not package_data["package_info"]["package_id"] in handled_packages: handled_packages.add(package_data["package_info"]["package_id"]) - yield package_data["package_info"] \ No newline at end of file + yield package_data["package_info"] -- cgit v1.2.3 From 58ee0d19fdd71fc8a085701003df3d45e6d9102e Mon Sep 17 00:00:00 2001 From: casper Date: Sat, 4 Dec 2021 10:28:25 +0100 Subject: Vertically align author components with manage buttons To comply with UX design cura 8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index ad49c650e8..6331c9f58c 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -328,7 +328,7 @@ Rectangle Label { id: authorBy - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignCenter text: catalog.i18nc("@label", "By") font: UM.Theme.getFont("default") @@ -339,7 +339,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignCenter text: packageData.authorName textFont: UM.Theme.getFont("default_bold") -- cgit v1.2.3 From 09709ede8b27f02ae0dc2f8131a15f9ca94183c6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 5 Dec 2021 15:11:35 +0100 Subject: Fix duplicate packages in get AlllocalPackages The helper class is needed because dict's aren't hashable which complicates the `in` check. Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 49 +++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 98e0e592aa..21cd0c69dc 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -23,7 +23,7 @@ class CuraPackageManager(PackageManager): self.installedPackagesChanged.connect(self._updateLocallyInstalledPackages) def _updateLocallyInstalledPackages(self): - self._locally_installed_packages = list(self.iterateAllLocalPackages()) + self._locally_installed_packages = self.getAllLocalPackages() @property def locally_installed_packages(self): @@ -66,24 +66,29 @@ class CuraPackageManager(PackageManager): return machine_with_materials, machine_with_qualities - def iterateAllLocalPackages(self) -> Generator[Dict[str, Any], None, None]: - """ A generator which returns an unordered list of all the PackageModels""" - handled_packages = {} - - for packages in self.getAllInstalledPackagesInfo().values(): - for package_info in packages: - handled_packages.add(package_info["package_id"]) - yield package_info - - # Get all to be removed package_info's. These packages are still used in the current session so the user might - # still want to interact with these. - for package_data in self.getPackagesToRemove().values(): - if not package_data["package_info"]["package_id"] in handled_packages: - handled_packages.add(package_data["package_info"]["package_id"]) - yield package_data["package_info"] - - # Get all to be installed package_info's. Since the user might want to interact with these - for package_data in self.getPackagesToInstall().values(): - if not package_data["package_info"]["package_id"] in handled_packages: - handled_packages.add(package_data["package_info"]["package_id"]) - yield package_data["package_info"] + def getAllLocalPackages(self) -> List[Dict[str, Any]]: + """ returns an unordered list of all the package_info installed, to be installed or to be returned""" + + class PkgInfo: + # Needed helper class because a dict isn't hashable + def __init__(self, package_info): + self._info = package_info + + def __contains__(self, item): + return item == self._info["package_id"] + + def __repr__(self): + return repr(self._info) + + def __iter__(self): + for k, v in self._info.items(): + yield k, v + + def asdict(self): + return self._info + + packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] + packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToRemove().values() if package["package_info"]["package_id"] not in packages]) + packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToInstall().values() if package["package_info"]["package_id"] not in packages]) + + return [dict(package) for package in packages] -- cgit v1.2.3 From bd2f2708034bddaf11a81e6775a1d21942b09f5f Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 5 Dec 2021 15:18:51 +0100 Subject: Added typing Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 19 +++++++++---------- plugins/Marketplace/PackageModel.py | 8 ++++---- plugins/Marketplace/RemotePackageList.py | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 3c86040a00..8c9bcdb963 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -9,11 +9,10 @@ from typing import Dict, Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope -from UM.TaskManagement.HttpRequestManager import HttpRequestData , HttpRequestManager +from UM.TaskManagement.HttpRequestManager import HttpRequestData, HttpRequestManager from UM.Logger import Logger from cura.CuraApplication import CuraApplication -from cura.API.Account import Account from cura import CuraPackageManager from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. @@ -36,7 +35,7 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._manager: CuraPackageManager = CuraApplication.getInstance().getPackageManager() - self._account: Account = CuraApplication.getInstance().getCuraAPI().account + self._account = CuraApplication.getInstance().getCuraAPI().account self._error_message = "" self.addRoleName(self.PackageRole, "package") self._is_loading = False @@ -113,7 +112,7 @@ class PackageList(ListModel): :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise""" return self._has_footer - def _connectManageButtonSignals(self, package): + def _connectManageButtonSignals(self, package: PackageModel) -> None: package.installPackageTriggered.connect(self.installPackage) package.uninstallPackageTriggered.connect(self.uninstallPackage) package.updatePackageTriggered.connect(self.installPackage) @@ -126,7 +125,7 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) - def download(self, package_id, url, update: bool = False): + def download(self, package_id: str, url: str, update: bool = False) -> None: def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) @@ -176,7 +175,7 @@ class PackageList(ListModel): def _install(self, package_id: str, update: bool = False) -> None: package_path = self._to_install.pop(package_id) Logger.debug(f"Installing {package_id}") - to_be_installed = self._manager.installPackage(package_path) != None + to_be_installed = self._manager.installPackage(package_path) is not None package = self._getPackageModel(package_id) if package.canUpdate and to_be_installed: package.canUpdate = False @@ -197,7 +196,7 @@ class PackageList(ListModel): ) @pyqtSlot(str) - def uninstallPackage(self, package_id): + def uninstallPackage(self, package_id: str) -> None: Logger.debug(f"Uninstalling {package_id}") package = self._getPackageModel(package_id) self._manager.removePackage(package_id) @@ -211,7 +210,7 @@ class PackageList(ListModel): HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) @pyqtSlot(str) - def updatePackage(self, package_id): + def updatePackage(self, package_id: str) -> None: self._manager.removePackage(package_id, force_add = True) package = self._getPackageModel(package_id) url = package.download_url @@ -219,9 +218,9 @@ class PackageList(ListModel): self.download(package_id, url, True) @pyqtSlot(str) - def enablePackage(self, package_id): + def enablePackage(self, package_id: str) -> None: Logger.debug(f"Enabling {package_id}") @pyqtSlot(str) - def disablePackage(self, package_id): + def disablePackage(self, package_id: str) -> None: Logger.debug(f"Disabling {package_id}") diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 3f602d1384..4a8254de7d 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -293,13 +293,13 @@ class PackageModel(QObject): isEnabledChanged = pyqtSignal() @pyqtProperty(bool, notify = isEnabledChanged) - def isEnabled(self): + def isEnabled(self) -> bool: return self._is_active manageEnableStateChanged = pyqtSignal() @pyqtProperty(str, notify = manageEnableStateChanged) - def manageEnableState(self): + def manageEnableState(self) -> str: # TODO: Handle manual installed packages if self._is_installed: if self._is_active: @@ -330,13 +330,13 @@ class PackageModel(QObject): manageUpdateStateChanged = pyqtSignal() @pyqtProperty(str, notify = manageUpdateStateChanged) - def manageUpdateState(self): + def manageUpdateState(self) -> str: if self._can_update: return "primary" return "hidden" @property - def canUpdate(self): + def canUpdate(self) -> bool: return self._can_update @canUpdate.setter diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index a8fb20e88f..fa05ca9526 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkReply -from typing import Optional, TYPE_CHECKING +from typing import Optional, Set, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Logger import Logger @@ -31,7 +31,7 @@ class RemotePackageList(PackageList): self._request_url = self._initialRequestUrl() self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() - self._locally_installed = { p["package_id"] for p in self._manager.locally_installed_packages } + self._locally_installed: Set[str] = { p["package_id"] for p in self._manager.locally_installed_packages } def __del__(self) -> None: """ -- cgit v1.2.3 From 305fb4ab09b6cfec6f930ead99240bf060a4d8c9 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 5 Dec 2021 16:23:23 +0100 Subject: renamed locally_installed property Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 20 ++++++++------------ plugins/Marketplace/LocalPackageList.py | 5 +++-- plugins/Marketplace/RemotePackageList.py | 4 ++-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 21cd0c69dc..4250af9072 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -19,22 +19,18 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) - self._locally_installed_packages = None - self.installedPackagesChanged.connect(self._updateLocallyInstalledPackages) + self._local_packages: Optional[List[Dict[str, Any]]] = None + self.installedPackagesChanged.connect(self._updateLocalPackages) - def _updateLocallyInstalledPackages(self): - self._locally_installed_packages = self.getAllLocalPackages() + def _updateLocalPackages(self) -> None: + self._local_packages = self.getAllLocalPackages() @property - def locally_installed_packages(self): + def local_packages(self) -> List[Dict[str, Any]]: """locally installed packages, lazy execution""" - if self._locally_installed_packages is None: - self._updateLocallyInstalledPackages() - return self._locally_installed_packages - - @locally_installed_packages.setter - def locally_installed_packages(self, value): - self._locally_installed_packages = value + if self._local_packages is None: + self._updateLocalPackages() + return self._local_packages def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 284f51c806..6bd22ee918 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -48,9 +48,10 @@ class LocalPackageList(PackageList): self.setIsLoading(True) # Obtain and sort the local packages - self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.locally_installed_packages]]) + Logger.debug(f"Number of local packages: {len(self._manager.local_packages)} -> {[p['package_id'] for p in self._manager.local_packages]}") + self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.local_packages]]) self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) - self.checkForUpdates(self._manager.locally_installed_packages) + self.checkForUpdates(self._manager.local_packages) self.setIsLoading(False) self.setHasMore(False) # All packages should have been loaded at this time diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index fa05ca9526..a994d3d2d2 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -31,7 +31,7 @@ class RemotePackageList(PackageList): self._request_url = self._initialRequestUrl() self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() - self._locally_installed: Set[str] = { p["package_id"] for p in self._manager.locally_installed_packages } + self._local_packages: Set[str] = { p["package_id"] for p in self._manager.local_packages } def __del__(self) -> None: """ @@ -129,7 +129,7 @@ class RemotePackageList(PackageList): return for package_data in response_data["data"]: - if package_data["package_id"] in self._locally_installed: + if package_data["package_id"] in self._local_packages: continue # We should only show packages which are not already installed try: package = PackageModel(package_data, parent = self) -- cgit v1.2.3 From a51b29cdf60a2c2677908056b1e850202ae52718 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 5 Dec 2021 16:28:21 +0100 Subject: Fixed double items in getAllLocalPackages Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 4250af9072..3ef24368fd 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -70,7 +70,7 @@ class CuraPackageManager(PackageManager): def __init__(self, package_info): self._info = package_info - def __contains__(self, item): + def __eq__(self, item): return item == self._info["package_id"] def __repr__(self): -- cgit v1.2.3 From 863e92d0d25c0835ef99872711e1b20d307ba36a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 09:14:40 +0100 Subject: Fixed state of manage buttons Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 7 +- plugins/Marketplace/PackageList.py | 98 +++++----- plugins/Marketplace/PackageModel.py | 210 ++++++++++++++------- plugins/Marketplace/resources/qml/ManageButton.qml | 2 +- plugins/Marketplace/resources/qml/PackageCard.qml | 10 +- 5 files changed, 208 insertions(+), 119 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 6bd22ee918..ea259dbdc7 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -48,9 +48,8 @@ class LocalPackageList(PackageList): self.setIsLoading(True) # Obtain and sort the local packages - Logger.debug(f"Number of local packages: {len(self._manager.local_packages)} -> {[p['package_id'] for p in self._manager.local_packages]}") self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.local_packages]]) - self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) + self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) self.checkForUpdates(self._manager.local_packages) self.setIsLoading(False) @@ -90,8 +89,8 @@ class LocalPackageList(PackageList): return for package_data in response_data["data"]: - package = self._getPackageModel(package_data["package_id"]) + package = self.getPackageModel(package_data["package_id"]) package.download_url = package_data.get("download_url", "") package.canUpdate = True - self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = 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 8c9bcdb963..5ad2c50ddb 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -112,19 +112,26 @@ class PackageList(ListModel): :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise""" return self._has_footer - def _connectManageButtonSignals(self, package: PackageModel) -> None: - package.installPackageTriggered.connect(self.installPackage) - package.uninstallPackageTriggered.connect(self.uninstallPackage) - package.updatePackageTriggered.connect(self.installPackage) - package.enablePackageTriggered.connect(self.enablePackage) - package.disablePackageTriggered.connect(self.disablePackage) - - def _getPackageModel(self, package_id: str) -> PackageModel: + def getPackageModel(self, package_id: str) -> PackageModel: index = self.find("package", package_id) return self.getItem(index)["package"] canInstallChanged = pyqtSignal(str, bool) + def _install(self, package_id: str, update: bool = False) -> None: + package_path = self._to_install.pop(package_id) + Logger.debug(f"Installing {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 = False + else: + package.is_recently_installed = True + package.is_installing = False + self.subscribeUserToPackage(package_id, str(package.sdk_version)) + def download(self, package_id: str, url: str, update: bool = False) -> None: def downloadFinished(reply: "QNetworkReply") -> None: @@ -159,34 +166,13 @@ class PackageList(ListModel): if reply: reply_string = bytes(reply.readAll()).decode() Logger.error(f"Failed to download package: {package_id} due to {reply_string}") - package = self._getPackageModel(package_id) + package = self.getPackageModel(package_id) if update: - package.setIsUpdating(False) + package.is_updating = False else: - package.setIsInstalling(False) - - @pyqtSlot(str) - def installPackage(self, package_id: str) -> None: - package = self._getPackageModel(package_id) - url = package.download_url - Logger.debug(f"Trying to download and install {package_id} from {url}") - self.download(package_id, url) + package.is_installing = False - def _install(self, package_id: str, update: bool = False) -> None: - package_path = self._to_install.pop(package_id) - Logger.debug(f"Installing {package_id}") - to_be_installed = self._manager.installPackage(package_path) is not None - package = self._getPackageModel(package_id) - if package.canUpdate and to_be_installed: - package.canUpdate = False - package.setManageInstallState(to_be_installed) - if update: - package.setIsUpdating(False) - else: - package.setIsInstalling(False) - self._subscribe(package_id, str(package.sdk_version)) - - def _subscribe(self, package_id: str, sdk_version: str) -> None: + def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: if self._account.isLoggedIn: Logger.debug(f"Subscribing the user for package: {package_id}") HttpRequestManager.getInstance().put( @@ -195,32 +181,58 @@ class PackageList(ListModel): scope = self._scope ) + def unsunscribeUserFromPackage(self, package_id: str) -> None: + if self._account.isLoggedIn: + Logger.debug(f"Unsubscribing the user for package: {package_id}") + HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) + + # --- Handle the manage package buttons --- + + def _connectManageButtonSignals(self, package: PackageModel) -> None: + package.installPackageTriggered.connect(self.installPackage) + package.uninstallPackageTriggered.connect(self.uninstallPackage) + package.updatePackageTriggered.connect(self.installPackage) + package.enablePackageTriggered.connect(self.enablePackage) + package.disablePackageTriggered.connect(self.disablePackage) + + @pyqtSlot(str) + def installPackage(self, package_id: str) -> None: + package = self.getPackageModel(package_id) + package.is_installing = True + url = package.download_url + Logger.debug(f"Trying to download and install {package_id} from {url}") + self.download(package_id, url) + @pyqtSlot(str) def uninstallPackage(self, package_id: str) -> None: Logger.debug(f"Uninstalling {package_id}") - package = self._getPackageModel(package_id) + package = self.getPackageModel(package_id) + package.is_installing = True self._manager.removePackage(package_id) - package.setIsInstalling(False) - package.setManageInstallState(False) - self._unsunscribe(package_id) - - def _unsunscribe(self, package_id: str) -> None: - if self._account.isLoggedIn: - Logger.debug(f"Unsubscribing the user for package: {package_id}") - HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) + package.is_installing = False + self.unsunscribeUserFromPackage(package_id) @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: + package = self.getPackageModel(package_id) + package.is_updating = True self._manager.removePackage(package_id, force_add = True) - package = self._getPackageModel(package_id) url = package.download_url Logger.debug(f"Trying to download and update {package_id} from {url}") self.download(package_id, url, True) @pyqtSlot(str) def enablePackage(self, package_id: str) -> None: + package = self.getPackageModel(package_id) + package.is_enabling = True Logger.debug(f"Enabling {package_id}") + # TODO: implement enabling functionality + package.is_enabling = False @pyqtSlot(str) def disablePackage(self, package_id: str) -> None: + package = self.getPackageModel(package_id) + package.is_enabling = True Logger.debug(f"Disabling {package_id}") + # TODO: implement disabling functionality + package.is_enabling = False diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 4a8254de7d..8ba712c19d 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -61,9 +61,11 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") - self._can_update = False self._is_installing = False + self.is_recently_installed = False + self._can_update = False self._is_updating = False + self._is_enabling = 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. @@ -262,63 +264,56 @@ class PackageModel(QObject): def isCompatibleAirManager(self) -> bool: return self._is_compatible_air_manager - isInstallingChanged = pyqtSignal() - - def setIsInstalling(self, value: bool) -> None: - if value != self._is_installing: - self._is_installing = value - self.isInstallingChanged.emit() - - @pyqtProperty(bool, fset = setIsInstalling, notify = isInstallingChanged) - def isInstalling(self) -> bool: - return self._is_installing - - isUpdatingChanged = pyqtSignal() + # --- manage buttons signals --- - def setIsUpdating(self, value: bool) -> None: - if value != self._is_updating: - self._is_updating = value - self.isUpdatingChanged.emit() + stateManageButtonChanged = pyqtSignal() - @pyqtProperty(bool, fset = setIsUpdating, notify = isUpdatingChanged) - def isUpdating(self) -> bool: - return self._is_updating + installPackageTriggered = pyqtSignal(str) - isInstalledChanged = pyqtSignal() + uninstallPackageTriggered = pyqtSignal(str) - @pyqtProperty(bool, notify = isInstalledChanged) - def isInstalled(self): - return self._is_installed + updatePackageTriggered = pyqtSignal(str) - isEnabledChanged = pyqtSignal() + enablePackageTriggered = pyqtSignal(str) - @pyqtProperty(bool, notify = isEnabledChanged) - def isEnabled(self) -> bool: - return self._is_active + disablePackageTriggered = pyqtSignal(str) - manageEnableStateChanged = pyqtSignal() + # --- enabling --- - @pyqtProperty(str, notify = manageEnableStateChanged) - def manageEnableState(self) -> str: - # TODO: Handle manual installed packages - if self._is_installed: - if self._is_active: + @pyqtProperty(str, notify = stateManageButtonChanged) + def stateManageEnableButton(self) -> str: + if self._is_enabling: + return "busy" + if self.is_recently_installed: + 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" - else: - return "primary" - else: return "hidden" + if not self._is_installed: + return "hidden" + if self._is_installed and self._is_active: + return "secondary" + return "primary" - 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: + @property + def is_enabling(self) -> bool: + return self._is_enabling + + @is_enabling.setter + def is_enabling(self, value: bool) -> None: + if value != self._is_enabling: + self._is_enabling = value + self.stateManageButtonChanged.emit() + + # --- Installing --- + + @pyqtProperty(str, notify = stateManageButtonChanged) + def stateManageInstallButton(self) -> str: + if self._is_installing: + return "busy" + if self.is_recently_installed: + return "secondary" if self._is_installed: if self._is_bundled: return "hidden" @@ -327,30 +322,117 @@ class PackageModel(QObject): else: return "primary" - manageUpdateStateChanged = pyqtSignal() + @property + def is_installing(self) -> bool: + return self._is_installing + + @is_installing.setter + def is_installing(self, value: bool) -> None: + if value != self._is_installing: + self._is_installing = value + self.stateManageButtonChanged.emit() + + # --- Updating --- - @pyqtProperty(str, notify = manageUpdateStateChanged) - def manageUpdateState(self) -> str: + @pyqtProperty(str, notify = stateManageButtonChanged) + def stateManageUpdateButton(self) -> str: + if self._is_updating: + return "busy" if self._can_update: return "primary" return "hidden" @property - def canUpdate(self) -> bool: + def is_updating(self) -> bool: + return self._is_updating + + @is_updating.setter + def is_updating(self, value: bool) -> None: + if value != self._is_updating: + self._is_updating = value + self.stateManageButtonChanged.emit() + + @property + def can_update(self) -> bool: return self._can_update - @canUpdate.setter - def canUpdate(self, value): + @can_update.setter + def can_update(self, value: bool) -> None: if value != self._can_update: self._can_update = value - self.manageUpdateStateChanged.emit() - - installPackageTriggered = pyqtSignal(str) - - uninstallPackageTriggered = pyqtSignal(str) - - updatePackageTriggered = pyqtSignal(str) - - enablePackageTriggered = pyqtSignal(str) - - disablePackageTriggered = pyqtSignal(str) + 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 797e83830f..dd17d07d01 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -17,7 +17,7 @@ RowLayout property string busySecondaryText: busyMessageText.text property string mainState: "primary" property bool enabled: true - property bool busy: false + property bool busy signal clicked(bool primary_action) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 6331c9f58c..89ae6091fc 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -355,12 +355,12 @@ Rectangle ManageButton { id: enableManageButton + state: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") busyPrimaryText: catalog.i18nc("@button", "enabling...") secondaryText: catalog.i18nc("@button", "Disable") busySecondaryText: catalog.i18nc("@button", "disabling...") - mainState: packageData.manageEnableState enabled: !(installManageButton.busy || updateManageButton.busy) } Connections @@ -382,13 +382,12 @@ Rectangle ManageButton { id: installManageButton + state: packageData.stateManageInstallButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") busyPrimaryText: catalog.i18nc("@button", "installing...") secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "uninstalling...") - mainState: packageData.manageInstallState - busy: packageData.isInstalling enabled: !(enableManageButton.busy || updateManageButton.busy) } Connections @@ -396,7 +395,6 @@ Rectangle target: installManageButton function onClicked(primary_action) { - packageData.isInstalling = true if (primary_action) { packageData.installPackageTriggered(packageData.packageId) @@ -411,11 +409,10 @@ Rectangle ManageButton { id: updateManageButton + state: packageData.stateManageUpdateButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "updating...") - mainState: packageData.manageUpdateState - busy: packageData.isUpdating enabled: !(installManageButton.busy || enableManageButton.busy) } Connections @@ -423,7 +420,6 @@ Rectangle target: updateManageButton function onClicked(primary_action) { - packageData.isUpdating = true packageData.updatePackageTriggered(packageData.packageId) } } -- cgit v1.2.3 From 2d9c557a13eb710f9d5de24d4e1aaf6505bc89ee Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 09:43:09 +0100 Subject: Only check for updates when logged in Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index ea259dbdc7..622a47429d 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -65,14 +65,15 @@ class LocalPackageList(PackageList): return package def checkForUpdates(self, packages: List[Dict[str, Any]]): - 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 - ) + 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]}" + + self._ongoing_request = HttpRequestManager.getInstance().get( + request_url, + scope = self._scope, + callback = self._parseResponse + ) def _parseResponse(self, reply: "QNetworkReply") -> None: """ -- cgit v1.2.3 From 325783ca46ea1f4de1819abdc5b9093c4b8aa2ee Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 10:19:14 +0100 Subject: Persistent handled state across Package Lists Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 5 ++++- plugins/Marketplace/PackageList.py | 11 +++++++---- plugins/Marketplace/PackageModel.py | 18 ++++++++++++++---- plugins/Marketplace/RemotePackageList.py | 3 +-- plugins/Marketplace/resources/qml/ManageButton.qml | 2 -- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 622a47429d..e6ad425954 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -57,10 +57,13 @@ 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 = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" + + bundled_or_installed = "bundled" if self._manager.isBundledPackage(package_info["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(): + package.is_recently_managed = True self._connectManageButtonSignals(package) return package diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 5ad2c50ddb..301f765dab 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -4,7 +4,7 @@ import tempfile import json from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import Dict, Optional, TYPE_CHECKING +from typing import Dict, Optional, Set, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel @@ -43,6 +43,7 @@ class PackageList(ListModel): self._has_footer = True self._to_install: Dict[str, str] = {} self.canInstallChanged.connect(self._install) + self._local_packages: Set[str] = {p["package_id"] for p in self._manager.local_packages} self._ongoing_request: Optional[HttpRequestData] = None self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) @@ -128,7 +129,8 @@ class PackageList(ListModel): if update: package.is_updating = False else: - package.is_recently_installed = True + 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)) @@ -201,7 +203,7 @@ class PackageList(ListModel): package.is_installing = True url = package.download_url Logger.debug(f"Trying to download and install {package_id} from {url}") - self.download(package_id, url) + self.download(package_id, url, False) @pyqtSlot(str) def uninstallPackage(self, package_id: str) -> None: @@ -209,8 +211,9 @@ class PackageList(ListModel): package = self.getPackageModel(package_id) package.is_installing = True self._manager.removePackage(package_id) - package.is_installing = False self.unsunscribeUserFromPackage(package_id) + package.is_installing = False + package.is_recently_managed = True @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 8ba712c19d..58184ac1c3 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -62,7 +62,7 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing = False - self.is_recently_installed = False + self._is_recently_managed = False self._can_update = False self._is_updating = False self._is_enabling = False @@ -284,7 +284,7 @@ class PackageModel(QObject): def stateManageEnableButton(self) -> str: if self._is_enabling: return "busy" - if self.is_recently_installed: + 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 @@ -312,8 +312,8 @@ class PackageModel(QObject): def stateManageInstallButton(self) -> str: if self._is_installing: return "busy" - if self.is_recently_installed: - return "secondary" + if self._is_recently_managed: + return "hidden" if self._is_installed: if self._is_bundled: return "hidden" @@ -322,6 +322,16 @@ class PackageModel(QObject): else: return "primary" + @property + def is_recently_managed(self) -> bool: + return self._is_recently_managed + + @is_recently_managed.setter + def is_recently_managed(self, value: bool) -> None: + if value != self._is_recently_managed: + self._is_recently_managed = value + self.stateManageButtonChanged.emit() + @property def is_installing(self) -> bool: return self._is_installing diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index a994d3d2d2..88e1c28045 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkReply -from typing import Optional, Set, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Logger import Logger @@ -31,7 +31,6 @@ class RemotePackageList(PackageList): self._request_url = self._initialRequestUrl() self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() - self._local_packages: Set[str] = { p["package_id"] for p in self._manager.local_packages } def __del__(self) -> None: """ diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index dd17d07d01..cdd52c9b90 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -21,8 +21,6 @@ RowLayout signal clicked(bool primary_action) - state: busy ? "busy" : mainState - Cura.PrimaryButton { id: primaryButton -- cgit v1.2.3 From 7a902f8314873336ea4f9a0ff65893e53e580850 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 11:28:06 +0100 Subject: Add restart required banner CURA-8587 --- plugins/Marketplace/resources/qml/Marketplace.qml | 47 +++++++++++++++++++++++ resources/themes/cura-light/theme.json | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 44f7777b35..98de8b0534 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -228,4 +228,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/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], -- cgit v1.2.3 From de0dc568cded847c6eb99fa7f328edb8ba760c27 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 11:30:27 +0100 Subject: Change rectangle into item It had no color, so it doesn't need to be a rectangle Cura-8587 --- plugins/Marketplace/resources/qml/Marketplace.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 98de8b0534..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 -- cgit v1.2.3 From ea85e59e501710478b592064420a183ecbac33b0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 11:34:19 +0100 Subject: Fix margin CURA-8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 89ae6091fc..31716ed005 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 } -- cgit v1.2.3 From 579cc7bdbc582687ce73efc1b0ac26b9da93a788 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 11:34:23 +0100 Subject: Enable or disable a plugin functionality added Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 8 ++++++-- plugins/Marketplace/PackageModel.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 301f765dab..965484faff 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -11,6 +11,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 import PluginRegistry from cura.CuraApplication import CuraApplication from cura import CuraPackageManager @@ -35,6 +36,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") @@ -229,7 +231,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) @@ -237,5 +240,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..f1b5b202a3 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -287,8 +287,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 +304,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) -- cgit v1.2.3 From 18837a32b8e4a23abae31eb4190074aef81932bf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 11:54:14 +0100 Subject: Move verifiedIcon to it's own file Cleaning up the size of the package card a bit --- plugins/Marketplace/resources/qml/PackageCard.qml | 38 +----------------- plugins/Marketplace/resources/qml/VerifiedIcon.qml | 45 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/VerifiedIcon.qml diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 31716ed005..e4f56632df 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -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 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 -- cgit v1.2.3 From 09710c2d9f8b51bcc330e2d27a53af925102181a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 12:02:14 +0100 Subject: Remove unneeded connection The connection isn't needed to handle a signal. There is an easier shorthand CURA-8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index e4f56632df..bf0dda217b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -328,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) @@ -355,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) { @@ -380,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) } } } -- cgit v1.2.3 From 28b6bfb72906057cbc5368494dc582c30e25bbf0 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 13:53:44 +0100 Subject: Fixed the update button busy state Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 17 +++-- plugins/Marketplace/PackageList.py | 3 +- plugins/Marketplace/PackageModel.py | 75 ---------------------- plugins/Marketplace/resources/qml/ManageButton.qml | 5 +- 4 files changed, 11 insertions(+), 89 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index e6ad425954..5b3c05609e 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -68,15 +68,14 @@ class LocalPackageList(PackageList): 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 +94,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 965484faff..6a931c1e4b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -131,7 +131,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)) @@ -195,7 +194,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) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index f1b5b202a3..5251f03524 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -379,78 +379,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 -- cgit v1.2.3 From 1cc3d374a02ef3bbc4140b6b1916b41e3013bb6f Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 14:11:31 +0100 Subject: Show the uninstall button for packages which can be downgraded to the bundled version Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 6 ++++-- plugins/Marketplace/PackageModel.py | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 5b3c05609e..a1f3f45e7e 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -58,12 +58,14 @@ 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 diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 5251f03524..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. @@ -323,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" @@ -350,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) -- cgit v1.2.3 From 09e221d64a3954b515e86dccb786b0a9101804f6 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 14:49:50 +0100 Subject: Add license dialog to the Marketplace cura 8587 --- plugins/Marketplace/LicenseModel.py | 66 +++++++++++++ plugins/Marketplace/PackageList.py | 39 +++++++- .../Marketplace/resources/qml/LicenseDialog.qml | 110 +++++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 plugins/Marketplace/LicenseModel.py create mode 100644 plugins/Marketplace/resources/qml/LicenseDialog.qml diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py new file mode 100644 index 0000000000..cb85b33430 --- /dev/null +++ b/plugins/Marketplace/LicenseModel.py @@ -0,0 +1,66 @@ +from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal +from UM.i18n import i18nCatalog + +catalog = i18nCatalog("cura") + +# Model for the LicenseDialog +class LicenseModel(QObject): + DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline") + ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree") + + dialogTitleChanged = pyqtSignal() + packageNameChanged = pyqtSignal() + licenseTextChanged = pyqtSignal() + iconChanged = pyqtSignal() + + def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None: + super().__init__() + + self._dialogTitle = "" + self._license_text = "" + self._package_name = "" + self._icon_url = "" + self._decline_button_text = decline_button_text + + @pyqtProperty(str, constant = True) + def acceptButtonText(self): + return self.ACCEPT_BUTTON_TEXT + + @pyqtProperty(str, constant = True) + def declineButtonText(self): + return self._decline_button_text + + @pyqtProperty(str, notify=dialogTitleChanged) + def dialogTitle(self) -> str: + return self._dialogTitle + + @pyqtProperty(str, notify=packageNameChanged) + def packageName(self) -> str: + return self._package_name + + def setPackageName(self, name: str) -> None: + self._package_name = name + self.packageNameChanged.emit() + + @pyqtProperty(str, notify=iconChanged) + def iconUrl(self) -> str: + return self._icon_url + + def setIconUrl(self, url: str): + self._icon_url = url + self.iconChanged.emit() + + @pyqtProperty(str, notify=licenseTextChanged) + def licenseText(self) -> str: + return self._license_text + + def setLicenseText(self, license_text: str) -> None: + if self._license_text != license_text: + self._license_text = license_text + self.licenseTextChanged.emit() + + def _updateDialogTitle(self): + self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement") + if self._page_count > 1: + self._dialogTitle = self._dialogTitle + " ({}/{})".format(self._current_page_idx + 1, self._page_count) + self.dialogTitleChanged.emit() diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 301f765dab..0c5895866c 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. import tempfile import json +import os.path from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt from typing import Dict, Optional, Set, TYPE_CHECKING @@ -11,6 +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 cura.CuraApplication import CuraApplication from cura import CuraPackageManager @@ -18,6 +20,7 @@ from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To ma from .PackageModel import PackageModel from .Constants import USER_PACKAGES_URL +from .LicenseModel import LicenseModel if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -42,12 +45,24 @@ class PackageList(ListModel): self._has_more = False self._has_footer = True self._to_install: Dict[str, str] = {} - self.canInstallChanged.connect(self._install) + self.canInstallChanged.connect(self._requestInstall) self._local_packages: Set[str] = {p["package_id"] for p in self._manager.local_packages} self._ongoing_request: Optional[HttpRequestData] = None self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + self._license_model = LicenseModel() + + plugin_path = PluginRegistry.getInstance().getPluginPath("Marketplace") + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + + # create a QML component for the license dialog + license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "LicenseDialog.qml") + self._license_dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, { + "licenseModel": self._license_model + }) + @pyqtSlot() def updatePackages(self) -> None: """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" @@ -119,6 +134,28 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) + def _openLicenseDialog(self, plugin_name: str, license_content: str, icon_url: str) -> None: + self._license_model.setIconUrl(icon_url) + self._license_model.setPackageName(plugin_name) + self._license_model.setLicenseText(license_content) + self._license_dialog.show() + + def _requestInstall(self, package_id: str, update: bool = False) -> None: + Logger.debug(f"Request installing {package_id}") + + package_path = self._to_install.pop(package_id) + license_content = self._manager.getPackageLicense(package_path) + + if not update and license_content is not None: + # open dialog, prompting the using to accept the plugin license + package = self.getPackageModel(package_id) + plugin_name = package.displayName + icon_url = package.iconUrl + self._openLicenseDialog(plugin_name, license_content, icon_url) + else: + # Otherwise continue the installation + self._install(package_id, update) + def _install(self, package_id: str, update: bool = False) -> None: package_path = self._to_install.pop(package_id) Logger.debug(f"Installing {package_id}") diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml new file mode 100644 index 0000000000..9219f4ed32 --- /dev/null +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -0,0 +1,110 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Toolbox is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +import QtQuick.Dialogs 1.1 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 + +import UM 1.1 as UM +import Cura 1.6 as Cura + +UM.Dialog +{ + id: licenseDialog + title: licenseModel.dialogTitle + minimumWidth: UM.Theme.getSize("license_window_minimum").width + minimumHeight: UM.Theme.getSize("license_window_minimum").height + width: minimumWidth + height: minimumHeight + backgroundColor: UM.Theme.getColor("main_background") + margin: screenScaleFactor * 10 + + ColumnLayout + { + anchors.fill: parent + spacing: UM.Theme.getSize("thick_margin").height + + UM.I18nCatalog{id: catalog; name: "cura"} + + Label + { + id: licenseHeader + Layout.fillWidth: true + text: catalog.i18nc("@label", "You need to accept the license to install the package") + color: UM.Theme.getColor("text") + wrapMode: Text.Wrap + renderType: Text.NativeRendering + } + + Row { + id: packageRow + + Layout.fillWidth: true + height: childrenRect.height + spacing: UM.Theme.getSize("default_margin").width + leftPadding: UM.Theme.getSize("narrow_margin").width + + Image + { + id: icon + width: 30 * screenScaleFactor + height: width + sourceSize.width: width + sourceSize.height: height + fillMode: Image.PreserveAspectFit + source: licenseModel.iconUrl || "../../images/placeholder.svg" + mipmap: true + } + + Label + { + id: packageName + text: licenseModel.packageName + color: UM.Theme.getColor("text") + font.bold: true + anchors.verticalCenter: icon.verticalCenter + height: contentHeight + wrapMode: Text.Wrap + renderType: Text.NativeRendering + } + + + } + + Cura.ScrollableTextArea + { + + Layout.fillWidth: true + Layout.fillHeight: true + anchors.topMargin: UM.Theme.getSize("default_margin").height + + textArea.text: licenseModel.licenseText + textArea.readOnly: true + } + + } + rightButtons: + [ + Cura.PrimaryButton + { + leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width + rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width + + text: licenseModel.acceptButtonText + onClicked: { handler.onLicenseAccepted() } + } + ] + + leftButtons: + [ + Cura.SecondaryButton + { + id: declineButton + text: licenseModel.declineButtonText + onClicked: { handler.onLicenseDeclined() } + } + ] +} -- cgit v1.2.3 From c3665d7440d1beaa1a826d1fc874cfe39b1f70aa Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 15:45:27 +0100 Subject: Correctly handle actions from marketplace license prompt Install package on accept And stop spinner on decline cura 8587 --- plugins/Marketplace/PackageList.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 0c5895866c..7bb1076988 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -60,7 +60,8 @@ class PackageList(ListModel): # create a QML component for the license dialog license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "LicenseDialog.qml") self._license_dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, { - "licenseModel": self._license_model + "licenseModel": self._license_model, + "handler": self }) @pyqtSlot() @@ -135,11 +136,32 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) def _openLicenseDialog(self, plugin_name: str, license_content: str, icon_url: str) -> None: + Logger.debug(f"Prompting license for {plugin_name}") self._license_model.setIconUrl(icon_url) self._license_model.setPackageName(plugin_name) self._license_model.setLicenseText(license_content) self._license_dialog.show() + @pyqtSlot() + def onLicenseAccepted(self) -> None: + package_id = self._to_be_installed_package_id + package_path = self._to_be_installed_package_path + del self._to_be_installed_package_id + del self._to_be_installed_package_path + Logger.debug(f"Accepted license for {package_id}") + self._license_dialog.close() + self._install(package_id, package_path) + + @pyqtSlot() + def onLicenseDeclined(self) -> None: + package_id = self._to_be_installed_package_id + del self._to_be_installed_package_id + del self._to_be_installed_package_path + Logger.debug(f"Declined license for {package_id}") + self._license_dialog.close() + package = self.getPackageModel(package_id) + package.is_installing = False + def _requestInstall(self, package_id: str, update: bool = False) -> None: Logger.debug(f"Request installing {package_id}") @@ -147,17 +169,23 @@ class PackageList(ListModel): license_content = self._manager.getPackageLicense(package_path) if not update and license_content is not None: + # If installation is not and update, and the packages contains a license then # open dialog, prompting the using to accept the plugin license + + # Store some properties that are needed after the dialog is closed + self._to_be_installed_package_id = package_id + self._to_be_installed_package_path = package_path + + # Open actual dialog package = self.getPackageModel(package_id) plugin_name = package.displayName icon_url = package.iconUrl self._openLicenseDialog(plugin_name, license_content, icon_url) else: # Otherwise continue the installation - self._install(package_id, update) + self._install(package_id, package_path, update) - def _install(self, package_id: str, update: bool = False) -> None: - package_path = self._to_install.pop(package_id) + def _install(self, package_id: str, package_path: str, update: bool = False) -> None: Logger.debug(f"Installing {package_id}") to_be_installed = self._manager.installPackage(package_path) is not None package = self.getPackageModel(package_id) -- cgit v1.2.3 From 0cc9d8db6875b4c3bc3521fbf6ca2e8ad9e52c90 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 15:51:19 +0100 Subject: Use existing plugin registry Fix after merge cura 8587 --- 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 a9d4e698ed..d50e19d514 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -54,7 +54,7 @@ class PackageList(ListModel): self._license_model = LicenseModel() - plugin_path = PluginRegistry.getInstance().getPluginPath("Marketplace") + plugin_path = self._plugin_registry.getPluginPath("Marketplace") if plugin_path is None: plugin_path = os.path.dirname(__file__) -- cgit v1.2.3 From 3cae6d80b9c06dfe1ca04e8bd7d38655d01fc10b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:00:51 +0100 Subject: Show restart banner only when a restart is required Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 62f9673072..bda153ee23 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -232,7 +232,7 @@ Window { height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("primary") - visible: false // TODO: enable this when restart is required + visible: CuraApplication.getPackageManager().hasPackagesToRemoveOrInstall anchors { left: parent.left -- cgit v1.2.3 From 3c5496441761f7475428b33d444a1c8050d8a980 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:02:42 +0100 Subject: Use same content for button as old one So make it dynamic based on the application name. This also causes the translation to be re-used. Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index bda153ee23..e765f0f009 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -270,7 +270,7 @@ Window Cura.SecondaryButton { id: quitButton - text: catalog.i18nc("@button", "Quit Ultimaker Cura") + text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) } } } -- cgit v1.2.3 From e646d53b539aacd2b0daea0a18fe15e1cb70c95a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:05:38 +0100 Subject: Make quit button quit Cura Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/Marketplace.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index e765f0f009..cde33faa97 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -271,6 +271,11 @@ Window { id: quitButton text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) + onClicked: + { + marketplaceDialog.hide(); + CuraApplication.closeApplication(); + } } } } -- cgit v1.2.3 From e3eb82b022f19d6f13cba5ff8be0858fed2f43ef Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:21:58 +0100 Subject: Align read more label to the text, not the parent box This is the same alignment as the ellipsis shown when the description is abbreviated. Looks correct, and it should be correct. Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index bf0dda217b..2112dc6a81 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -239,7 +239,7 @@ Rectangle { id: readMoreButton anchors.right: parent.right - anchors.bottom: parent.bottom + anchors.bottom: descriptionLabel.bottom height: fontMetrics.height //Height of a single line. text: catalog.i18nc("@info", "Read more") -- cgit v1.2.3 From c6501d6adeb1b155f35eca72626b8d2ed00a397d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:25:50 +0100 Subject: Use capital letters for busy text It seems though that the wrong text is shown here. In any case this looks more consistent. Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/PackageCard.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 2112dc6a81..7bfee42457 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -324,9 +324,9 @@ Rectangle state: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") - busyPrimaryText: catalog.i18nc("@button", "enabling...") + busyPrimaryText: catalog.i18nc("@button", "Inabling...") secondaryText: catalog.i18nc("@button", "Disable") - busySecondaryText: catalog.i18nc("@button", "disabling...") + busySecondaryText: catalog.i18nc("@button", "Disabling...") enabled: !(installManageButton.busy || updateManageButton.busy) onClicked: { @@ -347,9 +347,9 @@ Rectangle state: packageData.stateManageInstallButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") - busyPrimaryText: catalog.i18nc("@button", "installing...") + busyPrimaryText: catalog.i18nc("@button", "Installing...") secondaryText: catalog.i18nc("@button", "Uninstall") - busySecondaryText: catalog.i18nc("@button", "uninstalling...") + busySecondaryText: catalog.i18nc("@button", "Uninstalling...") enabled: !(enableManageButton.busy || updateManageButton.busy) onClicked: { -- cgit v1.2.3 From c73ef8439505c66a8630820517aa16db224cf9c8 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 6 Dec 2021 16:47:53 +0100 Subject: Show the correct busy message Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 323fea03f1..5e1aad03e7 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -28,6 +28,7 @@ RowLayout onClicked: { + busyMessageText.text = manageButton.busyPrimaryText manageButton.clicked(true) } } @@ -40,6 +41,7 @@ RowLayout onClicked: { + busyMessageText.text = manageButton.busySecondaryText manageButton.clicked(false) } } @@ -76,7 +78,6 @@ RowLayout { id: busyMessageText visible: parent.visible - text: manageButton.state == "primary" ? manageButton.busyPrimaryText : manageButton.busySecondaryText anchors.left: busyIndicator.right anchors.verticalCenter: parent.verticalCenter -- cgit v1.2.3 From f932239b6a0ecdb8a227b02878e8ddc4ff4e4fbe Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 16:52:38 +0100 Subject: Implement aesthetics of busy indicator Don't use the default spinner but use an image from our theme. A new image. And change the font size, colour and spacings a bit according to our designs. Contributes to issue CURA-8587. --- plugins/Marketplace/resources/qml/ManageButton.qml | 16 ++++++++++------ resources/themes/cura-light/icons/default/Spinner.svg | 3 +++ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 resources/themes/cura-light/icons/default/Spinner.svg diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 5e1aad03e7..39be04d386 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -53,21 +53,24 @@ RowLayout height: UM.Theme.getSize("action_button").height width: childrenRect.width - BusyIndicator + UM.RecolorImage { id: busyIndicator visible: parent.visible width: height anchors.left: parent.left anchors.top: parent.top + anchors.topMargin: UM.Theme.getSize("narrow_margin").height anchors.bottom: parent.bottom + anchors.bottomMargin: anchors.topMargin - palette.dark: UM.Theme.getColor("text") + source: UM.Theme.getIcon("Spinner") + color: UM.Theme.getColor("primary") RotationAnimator { - target: busyIndicator.contentItem - running: busyIndicator.visible && busyIndicator.running + target: busyIndicator + running: busyIndicator.visible from: 0 to: 360 loops: Animation.Infinite @@ -79,10 +82,11 @@ RowLayout id: busyMessageText visible: parent.visible anchors.left: busyIndicator.right + anchors.leftMargin: UM.Theme.getSize("narrow_margin").width anchors.verticalCenter: parent.verticalCenter - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("primary") } } diff --git a/resources/themes/cura-light/icons/default/Spinner.svg b/resources/themes/cura-light/icons/default/Spinner.svg new file mode 100644 index 0000000000..22a8f4dfd9 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Spinner.svg @@ -0,0 +1,3 @@ + + + -- cgit v1.2.3 From 4848c474e81bbe60a1e3a764c474d7a0efc821cb Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 17:10:13 +0100 Subject: Implement UX design for maketplace license dialog cura 8587 --- plugins/Marketplace/LicenseModel.py | 10 ------ plugins/Marketplace/PackageList.py | 6 ++-- .../Marketplace/resources/qml/LicenseDialog.qml | 40 +++++----------------- .../themes/cura-light/icons/high/Certificate.svg | 3 ++ resources/themes/cura-light/theme.json | 4 ++- 5 files changed, 17 insertions(+), 46 deletions(-) create mode 100644 resources/themes/cura-light/icons/high/Certificate.svg diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py index cb85b33430..199ddc9ee0 100644 --- a/plugins/Marketplace/LicenseModel.py +++ b/plugins/Marketplace/LicenseModel.py @@ -11,7 +11,6 @@ class LicenseModel(QObject): dialogTitleChanged = pyqtSignal() packageNameChanged = pyqtSignal() licenseTextChanged = pyqtSignal() - iconChanged = pyqtSignal() def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None: super().__init__() @@ -19,7 +18,6 @@ class LicenseModel(QObject): self._dialogTitle = "" self._license_text = "" self._package_name = "" - self._icon_url = "" self._decline_button_text = decline_button_text @pyqtProperty(str, constant = True) @@ -42,14 +40,6 @@ class LicenseModel(QObject): self._package_name = name self.packageNameChanged.emit() - @pyqtProperty(str, notify=iconChanged) - def iconUrl(self) -> str: - return self._icon_url - - def setIconUrl(self, url: str): - self._icon_url = url - self.iconChanged.emit() - @pyqtProperty(str, notify=licenseTextChanged) def licenseText(self) -> str: return self._license_text diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index d50e19d514..e41d28721c 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -136,9 +136,8 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) - def _openLicenseDialog(self, plugin_name: str, license_content: str, icon_url: str) -> None: + def _openLicenseDialog(self, plugin_name: str, license_content: str) -> None: Logger.debug(f"Prompting license for {plugin_name}") - self._license_model.setIconUrl(icon_url) self._license_model.setPackageName(plugin_name) self._license_model.setLicenseText(license_content) self._license_dialog.show() @@ -180,8 +179,7 @@ class PackageList(ListModel): # Open actual dialog package = self.getPackageModel(package_id) plugin_name = package.displayName - icon_url = package.iconUrl - self._openLicenseDialog(plugin_name, license_content, icon_url) + self._openLicenseDialog(plugin_name, license_content) else: # Otherwise continue the installation self._install(package_id, package_path, update) diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index 9219f4ed32..0640379895 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -20,7 +20,6 @@ UM.Dialog width: minimumWidth height: minimumHeight backgroundColor: UM.Theme.getColor("main_background") - margin: screenScaleFactor * 10 ColumnLayout { @@ -29,49 +28,32 @@ UM.Dialog UM.I18nCatalog{id: catalog; name: "cura"} - Label - { - id: licenseHeader - Layout.fillWidth: true - text: catalog.i18nc("@label", "You need to accept the license to install the package") - color: UM.Theme.getColor("text") - wrapMode: Text.Wrap - renderType: Text.NativeRendering - } - Row { - id: packageRow - Layout.fillWidth: true height: childrenRect.height spacing: UM.Theme.getSize("default_margin").width leftPadding: UM.Theme.getSize("narrow_margin").width - Image + UM.RecolorImage { - id: icon - width: 30 * screenScaleFactor - height: width - sourceSize.width: width - sourceSize.height: height - fillMode: Image.PreserveAspectFit - source: licenseModel.iconUrl || "../../images/placeholder.svg" - mipmap: true + width: UM.Theme.getSize("marketplace_large_icon").width + height: UM.Theme.getSize("marketplace_large_icon").height + color: UM.Theme.getColor("text") + source: UM.Theme.getIcon("Certificate", "high") } Label { - id: packageName - text: licenseModel.packageName + text: catalog.i18nc("@text", "Please read and agree with the plugin licence.") color: UM.Theme.getColor("text") - font.bold: true + font: UM.Theme.getFont("large") anchors.verticalCenter: icon.verticalCenter - height: contentHeight + height: UM.Theme.getSize("marketplace_large_icon").height + verticalAlignment: Qt.AlignVCenter wrapMode: Text.Wrap renderType: Text.NativeRendering } - } Cura.ScrollableTextArea @@ -90,9 +72,6 @@ UM.Dialog [ Cura.PrimaryButton { - leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width - rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width - text: licenseModel.acceptButtonText onClicked: { handler.onLicenseAccepted() } } @@ -102,7 +81,6 @@ UM.Dialog [ Cura.SecondaryButton { - id: declineButton text: licenseModel.declineButtonText onClicked: { handler.onLicenseDeclined() } } diff --git a/resources/themes/cura-light/icons/high/Certificate.svg b/resources/themes/cura-light/icons/high/Certificate.svg new file mode 100644 index 0000000000..b588bddd8b --- /dev/null +++ b/resources/themes/cura-light/icons/high/Certificate.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 149bfab308..8e9db0e9fe 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -686,6 +686,8 @@ "welcome_wizard_content_image_big": [18, 15], "welcome_wizard_cloud_content_image": [4, 4], - "banner_icon_size": [2.0, 2.0] + "banner_icon_size": [2.0, 2.0], + + "marketplace_large_icon": [4.0, 4.0] } } -- cgit v1.2.3 From e5257703321825d9ca488f7b60e18900b91fd869 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 17:14:02 +0100 Subject: Cast result to list after _updateLocalPackages Because the _updateLocalPackages function guarantees it to be a list now, not None any more. Contributes to issue CURA-8587. --- cura/CuraPackageManager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 3ef24368fd..e3595ef4bb 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict, List, Tuple, TYPE_CHECKING, Optional, Generator +from typing import Any, cast, Dict, List, Tuple, TYPE_CHECKING, Optional, Generator from cura.CuraApplication import CuraApplication #To find some resource types. from cura.Settings.GlobalStack import GlobalStack @@ -30,7 +30,9 @@ class CuraPackageManager(PackageManager): """locally installed packages, lazy execution""" if self._local_packages is None: self._updateLocalPackages() - return self._local_packages + # _updateLocalPackages always results in a list of packages, not None. + # It's guaranteed to be a list now. + return cast(List[Dict[str, Any]], self._local_packages) def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) -- cgit v1.2.3 From aec643fd44157e3101040beb513405280e2e1c77 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 17:22:07 +0100 Subject: Fix several typing issues Contributes to issue CURA-8587. --- plugins/Marketplace/PackageList.py | 7 ++++--- plugins/Marketplace/PackageModel.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 6a931c1e4b..665451ba18 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -4,7 +4,7 @@ import tempfile import json from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import Dict, Optional, Set, TYPE_CHECKING +from typing import cast, Dict, Optional, Set, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel @@ -14,7 +14,7 @@ from UM.Logger import Logger from UM import PluginRegistry from cura.CuraApplication import CuraApplication -from cura import CuraPackageManager +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 @@ -22,6 +22,7 @@ from .Constants import USER_PACKAGES_URL if TYPE_CHECKING: from PyQt5.QtCore import QObject + from PyQt5.QtNetwork import QNetworkReply catalog = i18nCatalog("cura") @@ -35,7 +36,7 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._manager: CuraPackageManager = CuraApplication.getInstance().getPackageManager() + self._manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() self._account = CuraApplication.getInstance().getCuraAPI().account self._error_message = "" diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 92daf310a3..f7682340c9 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -71,11 +71,13 @@ class PackageModel(QObject): 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 __eq__(self, other: Union[str, "PackageModel"]): + def __eq__(self, other: object): if isinstance(other, PackageModel): return other == self - else: + elif isinstance(other, str): return other == self._package_id + else: + return False def __repr__(self): return f"<{self._package_id} : {self._package_version} : {self._section_title}>" -- cgit v1.2.3 From a084b689b5de203758c60acad694c0c84ad157c2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 6 Dec 2021 17:26:12 +0100 Subject: Add one remaining forgotten type import Contributes to issue CURA-8587. --- plugins/Marketplace/LocalPackageList.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index a1f3f45e7e..196f3f19c6 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -8,6 +8,7 @@ from PyQt5.QtCore import pyqtSlot, QObject if TYPE_CHECKING: from PyQt5.QtCore import QObject + from PyQt5.QtNetwork import QNetworkReply from UM.i18n import i18nCatalog from UM.TaskManagement.HttpRequestManager import HttpRequestManager -- cgit v1.2.3 From 119b86b9579910e31d2ef7063aca86242e9df2a7 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 17:32:10 +0100 Subject: Remove page-count logic from LicenceModel title dialog cura 8587 --- plugins/Marketplace/LicenseModel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py index 199ddc9ee0..1bedb02cad 100644 --- a/plugins/Marketplace/LicenseModel.py +++ b/plugins/Marketplace/LicenseModel.py @@ -51,6 +51,4 @@ class LicenseModel(QObject): def _updateDialogTitle(self): self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement") - if self._page_count > 1: - self._dialogTitle = self._dialogTitle + " ({}/{})".format(self._current_page_idx + 1, self._page_count) self.dialogTitleChanged.emit() -- cgit v1.2.3 From 49a6a83fe93e864c53865319444dc388c277c66e Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 17:50:18 +0100 Subject: Move button text inside QML element cura 8587 --- plugins/Marketplace/LicenseModel.py | 23 +--------------------- .../Marketplace/resources/qml/LicenseDialog.qml | 6 +++--- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py index 1bedb02cad..64625185d7 100644 --- a/plugins/Marketplace/LicenseModel.py +++ b/plugins/Marketplace/LicenseModel.py @@ -5,32 +5,15 @@ catalog = i18nCatalog("cura") # Model for the LicenseDialog class LicenseModel(QObject): - DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline") - ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree") dialogTitleChanged = pyqtSignal() packageNameChanged = pyqtSignal() licenseTextChanged = pyqtSignal() - def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None: + def __init__(self, licence_text: str, package_name: str) -> None: super().__init__() - - self._dialogTitle = "" self._license_text = "" self._package_name = "" - self._decline_button_text = decline_button_text - - @pyqtProperty(str, constant = True) - def acceptButtonText(self): - return self.ACCEPT_BUTTON_TEXT - - @pyqtProperty(str, constant = True) - def declineButtonText(self): - return self._decline_button_text - - @pyqtProperty(str, notify=dialogTitleChanged) - def dialogTitle(self) -> str: - return self._dialogTitle @pyqtProperty(str, notify=packageNameChanged) def packageName(self) -> str: @@ -48,7 +31,3 @@ class LicenseModel(QObject): if self._license_text != license_text: self._license_text = license_text self.licenseTextChanged.emit() - - def _updateDialogTitle(self): - self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement") - self.dialogTitleChanged.emit() diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index 0640379895..6d863ecce5 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -14,7 +14,7 @@ import Cura 1.6 as Cura UM.Dialog { id: licenseDialog - title: licenseModel.dialogTitle + title: catalog.i18nc("@button", "Plugin license agreement") minimumWidth: UM.Theme.getSize("license_window_minimum").width minimumHeight: UM.Theme.getSize("license_window_minimum").height width: minimumWidth @@ -72,7 +72,7 @@ UM.Dialog [ Cura.PrimaryButton { - text: licenseModel.acceptButtonText + text: catalog.i18nc("@button", "Accept") onClicked: { handler.onLicenseAccepted() } } ] @@ -81,7 +81,7 @@ UM.Dialog [ Cura.SecondaryButton { - text: licenseModel.declineButtonText + text: catalog.i18nc("@button", "Decline") onClicked: { handler.onLicenseDeclined() } } ] -- cgit v1.2.3 From 28b21628b44748b0a8d52147e1b361a1f615a664 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 18:07:44 +0100 Subject: Remove unused package_name property and add package_id in license model The `_to_be_installed_package_id` and `_to_be_installed_package_path` can now be removed from the class as they can requested from the model itself. This we make sure that the we alway install the package for which the license was recently accepted. cura 8587 --- plugins/Marketplace/LicenseModel.py | 20 +++++++++----------- plugins/Marketplace/PackageList.py | 30 +++++++++--------------------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py index 64625185d7..8eb439d4d6 100644 --- a/plugins/Marketplace/LicenseModel.py +++ b/plugins/Marketplace/LicenseModel.py @@ -5,23 +5,21 @@ catalog = i18nCatalog("cura") # Model for the LicenseDialog class LicenseModel(QObject): - - dialogTitleChanged = pyqtSignal() - packageNameChanged = pyqtSignal() + packageIdChanged = pyqtSignal() licenseTextChanged = pyqtSignal() - def __init__(self, licence_text: str, package_name: str) -> None: + def __init__(self) -> None: super().__init__() self._license_text = "" - self._package_name = "" + self._package_id = "" - @pyqtProperty(str, notify=packageNameChanged) - def packageName(self) -> str: - return self._package_name + @pyqtProperty(str, notify=packageIdChanged) + def packageId(self) -> str: + return self._package_id - def setPackageName(self, name: str) -> None: - self._package_name = name - self.packageNameChanged.emit() + def setPackageId(self, name: str) -> None: + self._package_id = name + self.packageIdChanged.emit() @pyqtProperty(str, notify=licenseTextChanged) def licenseText(self) -> str: diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 9414bb9b28..39d1d9fab6 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -139,25 +139,20 @@ class PackageList(ListModel): def _openLicenseDialog(self, plugin_name: str, license_content: str) -> None: Logger.debug(f"Prompting license for {plugin_name}") - self._license_model.setPackageName(plugin_name) + self._license_model.setPackageId(plugin_name) self._license_model.setLicenseText(license_content) self._license_dialog.show() @pyqtSlot() def onLicenseAccepted(self) -> None: - package_id = self._to_be_installed_package_id - package_path = self._to_be_installed_package_path - del self._to_be_installed_package_id - del self._to_be_installed_package_path + package_id = self._license_model.packageId Logger.debug(f"Accepted license for {package_id}") self._license_dialog.close() - self._install(package_id, package_path) + self._install(package_id) @pyqtSlot() def onLicenseDeclined(self) -> None: - package_id = self._to_be_installed_package_id - del self._to_be_installed_package_id - del self._to_be_installed_package_path + package_id = self._license_model.packageId Logger.debug(f"Declined license for {package_id}") self._license_dialog.close() package = self.getPackageModel(package_id) @@ -166,27 +161,20 @@ class PackageList(ListModel): def _requestInstall(self, package_id: str, update: bool = False) -> None: Logger.debug(f"Request installing {package_id}") - package_path = self._to_install.pop(package_id) + package_path = self._to_install[package_id] license_content = self._manager.getPackageLicense(package_path) if not update and license_content is not None: # If installation is not and update, and the packages contains a license then # open dialog, prompting the using to accept the plugin license - - # Store some properties that are needed after the dialog is closed - self._to_be_installed_package_id = package_id - self._to_be_installed_package_path = package_path - - # Open actual dialog - package = self.getPackageModel(package_id) - plugin_name = package.displayName - self._openLicenseDialog(plugin_name, license_content) + self._openLicenseDialog(package_id, license_content) else: # Otherwise continue the installation - self._install(package_id, package_path, update) + self._install(package_id, update) - def _install(self, package_id: str, package_path: str, update: bool = False) -> None: + def _install(self, package_id: str, update: bool = False) -> None: Logger.debug(f"Installing {package_id}") + 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: -- cgit v1.2.3 From 455f6f17915485ac5e778f3d3b03cb720a839fe6 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 18:12:58 +0100 Subject: Update licencing comment cura 8587 --- plugins/Marketplace/resources/qml/LicenseDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index 6d863ecce5..7bee6f9108 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -1,5 +1,5 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. +//Copyright (c) 2021 Ultimaker B.V. +//Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 import QtQuick.Dialogs 1.1 -- cgit v1.2.3 From 71a43060a60d697c4cad2394f919492560de9a48 Mon Sep 17 00:00:00 2001 From: casper Date: Mon, 6 Dec 2021 19:34:02 +0100 Subject: Open separate license dialog with each plugin install Previously the license dialog was instanciated once and re-used for each install. As the dialog is only shown after the plugin is downloaded it was possible to click install for multiple plugins. Plugins that finish downloading later would override the dialog of earlier downloaded plugins. Clicking "accept" would then only install the latest downloaded plugin. Now for each install a separate dialog is shown. Accepting the license agreement would only install the recently accepted plugin. Note: in the current form the license agreement does not show any identification to what plugin triggered the dialog. As multiple dialogs can be shown at once this might be unclear. cura 8587 --- plugins/Marketplace/LicenseModel.py | 31 ------------ plugins/Marketplace/PackageList.py | 59 ++++++++++++---------- .../Marketplace/resources/qml/LicenseDialog.qml | 6 +-- 3 files changed, 35 insertions(+), 61 deletions(-) delete mode 100644 plugins/Marketplace/LicenseModel.py diff --git a/plugins/Marketplace/LicenseModel.py b/plugins/Marketplace/LicenseModel.py deleted file mode 100644 index 8eb439d4d6..0000000000 --- a/plugins/Marketplace/LicenseModel.py +++ /dev/null @@ -1,31 +0,0 @@ -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal -from UM.i18n import i18nCatalog - -catalog = i18nCatalog("cura") - -# Model for the LicenseDialog -class LicenseModel(QObject): - packageIdChanged = pyqtSignal() - licenseTextChanged = pyqtSignal() - - def __init__(self) -> None: - super().__init__() - self._license_text = "" - self._package_id = "" - - @pyqtProperty(str, notify=packageIdChanged) - def packageId(self) -> str: - return self._package_id - - def setPackageId(self, name: str) -> None: - self._package_id = name - self.packageIdChanged.emit() - - @pyqtProperty(str, notify=licenseTextChanged) - def licenseText(self) -> str: - return self._license_text - - def setLicenseText(self, license_text: str) -> None: - if self._license_text != license_text: - self._license_text = license_text - self.licenseTextChanged.emit() diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 39d1d9fab6..f0a6d1de06 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -20,7 +20,6 @@ from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To ma from .PackageModel import PackageModel from .Constants import USER_PACKAGES_URL -from .LicenseModel import LicenseModel if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -52,19 +51,7 @@ class PackageList(ListModel): self._ongoing_request: Optional[HttpRequestData] = None self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) - - self._license_model = LicenseModel() - - plugin_path = self._plugin_registry.getPluginPath("Marketplace") - if plugin_path is None: - plugin_path = os.path.dirname(__file__) - - # create a QML component for the license dialog - license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "LicenseDialog.qml") - self._license_dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, { - "licenseModel": self._license_model, - "handler": self - }) + self._license_dialogs: Dict[str, QObject] = {} @pyqtSlot() def updatePackages(self) -> None: @@ -137,24 +124,42 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) - def _openLicenseDialog(self, plugin_name: str, license_content: str) -> None: - Logger.debug(f"Prompting license for {plugin_name}") - self._license_model.setPackageId(plugin_name) - self._license_model.setLicenseText(license_content) - self._license_dialog.show() + def _openLicenseDialog(self, package_id: str, license_content: str) -> None: + Logger.debug(f"Prompting license for {package_id}") - @pyqtSlot() - def onLicenseAccepted(self) -> None: - package_id = self._license_model.packageId + plugin_path = self._plugin_registry.getPluginPath("Marketplace") + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + + # create a QML component for the license dialog + license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "LicenseDialog.qml") + dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, { + "licenseContent": license_content, + "packageId": package_id, + "handler": self + }) + dialog.show() + # place dialog in class such that it does not get remove by garbage collector + self._license_dialogs[package_id] = dialog + + @pyqtSlot(str) + def onLicenseAccepted(self, package_id: str) -> None: Logger.debug(f"Accepted license for {package_id}") - self._license_dialog.close() + # close dialog + dialog = self._license_dialogs.pop(package_id) + if dialog is not None: + dialog.deleteLater() + # install relevant package self._install(package_id) - @pyqtSlot() - def onLicenseDeclined(self) -> None: - package_id = self._license_model.packageId + @pyqtSlot(str) + def onLicenseDeclined(self, package_id: str) -> None: Logger.debug(f"Declined license for {package_id}") - self._license_dialog.close() + # close dialog + dialog = self._license_dialogs.pop(package_id) + if dialog is not None: + dialog.deleteLater() + # reset package card package = self.getPackageModel(package_id) package.is_installing = False diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index 7bee6f9108..32c4ec699c 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -63,7 +63,7 @@ UM.Dialog Layout.fillHeight: true anchors.topMargin: UM.Theme.getSize("default_margin").height - textArea.text: licenseModel.licenseText + textArea.text: licenseContent textArea.readOnly: true } @@ -73,7 +73,7 @@ UM.Dialog Cura.PrimaryButton { text: catalog.i18nc("@button", "Accept") - onClicked: { handler.onLicenseAccepted() } + onClicked: { handler.onLicenseAccepted(packageId) } } ] @@ -82,7 +82,7 @@ UM.Dialog Cura.SecondaryButton { text: catalog.i18nc("@button", "Decline") - onClicked: { handler.onLicenseDeclined() } + onClicked: { handler.onLicenseDeclined(packageId) } } ] } -- cgit v1.2.3 From dc03a7fdcb99ea8df3ca1a3404a12a78a0499587 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 7 Dec 2021 08:18:26 +0100 Subject: Correctly handle accept and reject hotkeys in marketplace license dialog Accept license on "enter" Reject license on "escape" or dialog close cura 8587 --- plugins/Marketplace/resources/qml/LicenseDialog.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index 32c4ec699c..d37fe38bd9 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -85,4 +85,8 @@ UM.Dialog onClicked: { handler.onLicenseDeclined(packageId) } } ] + + onAccepted: { handler.onLicenseAccepted(packageId) } + onRejected: { handler.onLicenseDeclined(packageId) } + onClosing: { handler.onLicenseDeclined(packageId) } } -- cgit v1.2.3 From 0fefe85fca7518d4fa22992511d884d02e40ac28 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 08:57:49 +0100 Subject: Present restart banner if plugin has been en-/disabled Contributes to: CURA-8587 --- plugins/Marketplace/Marketplace.py | 15 +++++++++------ plugins/Marketplace/resources/qml/Marketplace.qml | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 89ad986920..228ee1e506 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. import os.path -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING @@ -18,14 +18,16 @@ if TYPE_CHECKING: from PyQt5.QtCore import QObject -class Marketplace(Extension): +class Marketplace(Extension, QObject): """ The main managing object for the Marketplace plug-in. """ - def __init__(self) -> None: - super().__init__() - self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. + def __init__(self, parent: Optional[QObject] = None) -> None: + QObject.__init__(self, parent = parent) + Extension.__init__(self) + self._window: Optional[QObject] = None # If the window has been loaded yet, it'll be cached in here. + self.plugin_registry: Optional[PluginRegistry] = None qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") @@ -38,11 +40,12 @@ class Marketplace(Extension): If the window hadn't been loaded yet into Qt, it will be created lazily. """ if self._window is None: + self.plugin_registry = PluginRegistry.getInstance() plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) if plugin_path is None: plugin_path = os.path.dirname(__file__) path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {}) + self._window = CuraApplication.getInstance().createQmlComponent(path, {"plugin_registry": self.plugin_registry}) if self._window is None: # Still None? Failed to load the QML then. return self._window.show() diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index cde33faa97..5c56b0c41d 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -232,7 +232,7 @@ Window { height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("primary") - visible: CuraApplication.getPackageManager().hasPackagesToRemoveOrInstall + visible: CuraApplication.getPackageManager().hasPackagesToRemoveOrInstall || plugin_registry.hasPluginsEnabledOrDisabled anchors { left: parent.left -- cgit v1.2.3 From f6966c25fb28214cedaf9ff84a22624fb013c11b Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 09:48:48 +0100 Subject: Some final tweaks and added missing documentation Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 8 ++--- plugins/Marketplace/Constants.py | 1 + plugins/Marketplace/Marketplace.py | 2 +- plugins/Marketplace/PackageList.py | 35 ++++++++++++++++++++++ plugins/Marketplace/PackageModel.py | 19 +++++++----- .../Marketplace/resources/qml/LicenseDialog.qml | 22 +++++++------- plugins/Toolbox/src/CloudSync/CloudApiClient.py | 2 +- 7 files changed, 65 insertions(+), 24 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index e3595ef4bb..bca6494f37 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,13 +1,13 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, cast, Dict, List, Tuple, TYPE_CHECKING, Optional, Generator +from typing import Any, cast, Dict, List, Tuple, TYPE_CHECKING, Optional -from cura.CuraApplication import CuraApplication #To find some resource types. +from cura.CuraApplication import CuraApplication # To find some resource types. from cura.Settings.GlobalStack import GlobalStack -from UM.PackageManager import PackageManager #The class we're extending. -from UM.Resources import Resources #To find storage paths for some resource types. +from UM.PackageManager import PackageManager # The class we're extending. +from UM.Resources import Resources # To find storage paths for some resource types. from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") diff --git a/plugins/Marketplace/Constants.py b/plugins/Marketplace/Constants.py index bc6d1f05fa..9f0f78b966 100644 --- a/plugins/Marketplace/Constants.py +++ b/plugins/Marketplace/Constants.py @@ -1,5 +1,6 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from cura.UltimakerCloud import UltimakerCloudConstants from cura.ApplicationMetadata import CuraSDKVersion diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 228ee1e506..d55e538f6c 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. import os.path -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index f0a6d1de06..346b7a2b67 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -192,6 +192,12 @@ class PackageList(ListModel): self.subscribeUserToPackage(package_id, str(package.sdk_version)) def download(self, package_id: str, url: str, update: bool = False) -> None: + """Initiate the download request + + :param package_id: the package identification string + :param url: the URL from which the package needs to be obtained + :param update: A flag if this is download request is an update process + """ def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) @@ -232,6 +238,11 @@ class PackageList(ListModel): package.is_installing = False def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: + """Subscribe the user (if logged in) to the package for a given SDK + + :param package_id: the package identification string + :param sdk_version: the SDK version + """ if self._account.isLoggedIn: Logger.debug(f"Subscribing the user for package: {package_id}") HttpRequestManager.getInstance().put( @@ -241,6 +252,10 @@ class PackageList(ListModel): ) def unsunscribeUserFromPackage(self, package_id: str) -> None: + """Unsubscribe the user (if logged in) from the package + + :param package_id: the package identification string + """ if self._account.isLoggedIn: Logger.debug(f"Unsubscribing the user for package: {package_id}") HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) @@ -256,6 +271,10 @@ class PackageList(ListModel): @pyqtSlot(str) def installPackage(self, package_id: str) -> None: + """Install a package from the Marketplace + + :param package_id: the package identification string + """ package = self.getPackageModel(package_id) package.is_installing = True url = package.download_url @@ -264,6 +283,10 @@ class PackageList(ListModel): @pyqtSlot(str) def uninstallPackage(self, package_id: str) -> None: + """Uninstall a package from the Marketplace + + :param package_id: the package identification string + """ Logger.debug(f"Uninstalling {package_id}") package = self.getPackageModel(package_id) package.is_installing = True @@ -274,6 +297,10 @@ class PackageList(ListModel): @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: + """Update a package from the Marketplace + + :param package_id: the package identification string + """ package = self.getPackageModel(package_id) package.is_updating = True self._manager.removePackage(package_id, force_add = True) @@ -283,6 +310,10 @@ class PackageList(ListModel): @pyqtSlot(str) def enablePackage(self, package_id: str) -> None: + """Enable a package in the plugin registry + + :param package_id: the package identification string + """ package = self.getPackageModel(package_id) package.is_enabling = True Logger.debug(f"Enabling {package_id}") @@ -292,6 +323,10 @@ class PackageList(ListModel): @pyqtSlot(str) def disablePackage(self, package_id: str) -> None: + """Disable a package in the plugin registry + + :param package_id: the package identification string + """ package = self.getPackageModel(package_id) package.is_enabling = True Logger.debug(f"Disabling {package_id}") diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index f7682340c9..2231f6adbd 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -3,10 +3,9 @@ from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal import re -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. -from UM.Logger import Logger from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. catalog = i18nCatalog("cura") @@ -285,13 +284,10 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageEnableButton(self) -> str: + """The state of the manage Enable Button of this package""" if self._is_enabling: return "busy" - if self._is_recently_managed: - return "hidden" - if self._package_type == "material": - return "hidden" - if not self._is_installed: + if self._is_recently_managed or self._package_type == "material" or not self._is_installed: return "hidden" if self._is_installed and self._is_active: return "secondary" @@ -299,6 +295,7 @@ class PackageModel(QObject): @property def is_enabling(self) -> bool: + """Flag if the package is being enabled/disabled""" return self._is_enabling @is_enabling.setter @@ -309,6 +306,7 @@ class PackageModel(QObject): @property def is_active(self) -> bool: + """Flag if the package is currently active""" return self._is_active @is_active.setter @@ -321,6 +319,7 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageInstallButton(self) -> str: + """The state of the Manage Install package card""" if self._is_installing: return "busy" if self._is_recently_managed: @@ -335,6 +334,7 @@ class PackageModel(QObject): @property def is_recently_managed(self) -> bool: + """Flag if the package has been recently managed by the user, either un-/installed updated etc""" return self._is_recently_managed @is_recently_managed.setter @@ -345,6 +345,7 @@ class PackageModel(QObject): @property def is_installing(self) -> bool: + """Flag is we're currently installing""" return self._is_installing @is_installing.setter @@ -355,6 +356,7 @@ class PackageModel(QObject): @property def can_downgrade(self) -> bool: + """Flag if the installed package can be downgraded to a bundled version""" return self._can_downgrade @can_downgrade.setter @@ -367,6 +369,7 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageUpdateButton(self) -> str: + """The state of the manage Update button for this card """ if self._is_updating: return "busy" if self._can_update: @@ -375,6 +378,7 @@ class PackageModel(QObject): @property def is_updating(self) -> bool: + """Flag indicating if the package is being updated""" return self._is_updating @is_updating.setter @@ -385,6 +389,7 @@ class PackageModel(QObject): @property def can_update(self) -> bool: + """Flag indicating if the package can be updated""" return self._can_update @can_update.setter diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index d37fe38bd9..d44ef335eb 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -8,7 +8,7 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM +import UM 1.6 as UM import Cura 1.6 as Cura UM.Dialog @@ -21,14 +21,15 @@ UM.Dialog height: minimumHeight backgroundColor: UM.Theme.getColor("main_background") + property variant catalog: UM.I18nCatalog { name: "cura" } + ColumnLayout { anchors.fill: parent spacing: UM.Theme.getSize("thick_margin").height - UM.I18nCatalog{id: catalog; name: "cura"} - - Row { + Row + { Layout.fillWidth: true height: childrenRect.height spacing: UM.Theme.getSize("default_margin").width @@ -36,6 +37,7 @@ UM.Dialog UM.RecolorImage { + id: icon width: UM.Theme.getSize("marketplace_large_icon").width height: UM.Theme.getSize("marketplace_large_icon").height color: UM.Theme.getColor("text") @@ -53,12 +55,10 @@ UM.Dialog wrapMode: Text.Wrap renderType: Text.NativeRendering } - } Cura.ScrollableTextArea { - Layout.fillWidth: true Layout.fillHeight: true anchors.topMargin: UM.Theme.getSize("default_margin").height @@ -73,7 +73,7 @@ UM.Dialog Cura.PrimaryButton { text: catalog.i18nc("@button", "Accept") - onClicked: { handler.onLicenseAccepted(packageId) } + onClicked: handler.onLicenseAccepted(packageId) } ] @@ -82,11 +82,11 @@ UM.Dialog Cura.SecondaryButton { text: catalog.i18nc("@button", "Decline") - onClicked: { handler.onLicenseDeclined(packageId) } + onClicked: handler.onLicenseDeclined(packageId) } ] - onAccepted: { handler.onLicenseAccepted(packageId) } - onRejected: { handler.onLicenseDeclined(packageId) } - onClosing: { handler.onLicenseDeclined(packageId) } + onAccepted: handler.onLicenseAccepted(packageId) + onRejected: handler.onLicenseDeclined(packageId) + onClosing: handler.onLicenseDeclined(packageId) } diff --git a/plugins/Toolbox/src/CloudSync/CloudApiClient.py b/plugins/Toolbox/src/CloudSync/CloudApiClient.py index 21eb1bdbd2..9543ec012e 100644 --- a/plugins/Toolbox/src/CloudSync/CloudApiClient.py +++ b/plugins/Toolbox/src/CloudSync/CloudApiClient.py @@ -38,7 +38,7 @@ class CloudApiClient: def _subscribe(self, package_id: str) -> None: """You probably don't want to use this directly. All installed packages will be automatically subscribed.""" - Logger.debug("Subscribing to {}", package_id) + Logger.debug("Subscribing to using the Old Toolbox {}", package_id) data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, CloudApiModel.sdk_version) HttpRequestManager.getInstance().put( url = CloudApiModel.api_url_user_packages, -- cgit v1.2.3 From 1c0e484069cfef1e6cd063684724d978506b95c6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 11:25:16 +0100 Subject: Only show manage buttons in manage tab or detail view Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/ManagedPackages.qml | 1 + plugins/Marketplace/resources/qml/Materials.qml | 1 + plugins/Marketplace/resources/qml/PackageCard.qml | 7 +++++++ plugins/Marketplace/resources/qml/Packages.qml | 2 ++ plugins/Marketplace/resources/qml/Plugins.qml | 1 + 5 files changed, 12 insertions(+) diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index f44fbd0a9b..dbdc04bf52 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -20,6 +20,7 @@ Packages bannerVisible = false; } searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" + packagesManageableInListView: true model: Marketplace.LocalPackageList { diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 39d283b0a5..d19f3a4b04 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -17,6 +17,7 @@ Packages bannerVisible = false; } searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-materials-browser" + packagesManageableInListView: false model: Marketplace.RemotePackageList { diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 7bfee42457..326cf0583f 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -10,8 +10,10 @@ import Cura 1.6 as Cura Rectangle { + id: root property var packageData property bool expanded: false + property bool manageableInListView height: childrenRect.height color: UM.Theme.getColor("main_background") @@ -328,6 +330,7 @@ Rectangle secondaryText: catalog.i18nc("@button", "Disable") busySecondaryText: catalog.i18nc("@button", "Disabling...") enabled: !(installManageButton.busy || updateManageButton.busy) + visible: root.manageableInListView || root.expanded onClicked: { if (primary_action) @@ -351,6 +354,8 @@ Rectangle secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "Uninstalling...") enabled: !(enableManageButton.busy || updateManageButton.busy) + visible: root.manageableInListView || root.expanded + onClicked: { if (primary_action) @@ -372,6 +377,8 @@ Rectangle primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "updating...") enabled: !(installManageButton.busy || enableManageButton.busy) + visible: root.manageableInListView || root.expanded + onClicked: packageData.updatePackageTriggered(packageData.packageId) } } diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index c240141a71..62c0f92149 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -19,6 +19,7 @@ ListView property string bannerText property string bannerReadMoreUrl property var onRemoveBanner + property bool packagesManageableInListView clip: true @@ -80,6 +81,7 @@ ListView PackageCard { + manageableInListView: packages.packagesManageableInListView packageData: model.package width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width color: cardMouseArea.containsMouse ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("main_background") diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 538afc827a..3cfa92d134 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -17,6 +17,7 @@ Packages bannerVisible = false; } searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" + packagesManageableInListView: false model: Marketplace.RemotePackageList { -- cgit v1.2.3 From ca76bcc29cc573b777c72ace624e384f78eff3d6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 12:23:04 +0100 Subject: Show a conformation message after a successful manage action Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 2 - plugins/Marketplace/PackageModel.py | 32 +++++----- plugins/Marketplace/resources/qml/ManageButton.qml | 68 +++++++++++++++++++++- plugins/Marketplace/resources/qml/PackageCard.qml | 11 +++- 4 files changed, 91 insertions(+), 22 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 346b7a2b67..47b5e8ff4b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -187,7 +187,6 @@ class PackageList(ListModel): if update: package.is_updating = False else: - package.is_recently_managed = True package.is_installing = False self.subscribeUserToPackage(package_id, str(package.sdk_version)) @@ -293,7 +292,6 @@ class PackageList(ListModel): self._manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) package.is_installing = False - package.is_recently_managed = True @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 2231f6adbd..6b313eb9a5 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -61,7 +61,10 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing = False - self._is_recently_managed = False + self._is_recently_installed = False + self._is_recently_updated = False + self._is_recently_enabled = False + self._can_update = False self._is_updating = False self._is_enabling = False @@ -287,7 +290,9 @@ class PackageModel(QObject): """The state of the manage Enable Button of this package""" if self._is_enabling: return "busy" - if self._is_recently_managed or self._package_type == "material" or not self._is_installed: + if self._is_recently_enabled: + return "confirmed" + if self._package_type == "material" or not self._is_installed: return "hidden" if self._is_installed and self._is_active: return "secondary" @@ -301,6 +306,8 @@ class PackageModel(QObject): @is_enabling.setter def is_enabling(self, value: bool) -> None: if value != self._is_enabling: + if not value: + self._is_recently_enabled = True self._is_enabling = value self.stateManageButtonChanged.emit() @@ -322,8 +329,8 @@ class PackageModel(QObject): """The state of the Manage Install package card""" if self._is_installing: return "busy" - if self._is_recently_managed: - return "hidden" + if self._is_recently_installed: + return "confirmed" if self._is_installed: if self._is_bundled and not self._can_downgrade: return "hidden" @@ -332,17 +339,6 @@ class PackageModel(QObject): else: return "primary" - @property - def is_recently_managed(self) -> bool: - """Flag if the package has been recently managed by the user, either un-/installed updated etc""" - return self._is_recently_managed - - @is_recently_managed.setter - def is_recently_managed(self, value: bool) -> None: - if value != self._is_recently_managed: - self._is_recently_managed = value - self.stateManageButtonChanged.emit() - @property def is_installing(self) -> bool: """Flag is we're currently installing""" @@ -351,6 +347,8 @@ class PackageModel(QObject): @is_installing.setter def is_installing(self, value: bool) -> None: if value != self._is_installing: + if not value: + self._is_recently_installed = True self._is_installing = value self.stateManageButtonChanged.emit() @@ -372,6 +370,8 @@ class PackageModel(QObject): """The state of the manage Update button for this card """ if self._is_updating: return "busy" + if self._is_recently_updated: + return "confirmed" if self._can_update: return "primary" return "hidden" @@ -384,6 +384,8 @@ class PackageModel(QObject): @is_updating.setter def is_updating(self, value: bool) -> None: if value != self._is_updating: + if not value: + self._is_recently_updated = True self._is_updating = value self.stateManageButtonChanged.emit() diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 39be04d386..67532bc9aa 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -15,6 +15,8 @@ RowLayout property alias secondaryText: secondaryButton.text property string busyPrimaryText: busyMessageText.text property string busySecondaryText: busyMessageText.text + property string confirmedPrimaryText: confirmedMessageText.text + property string confirmedSecondaryText: confirmedMessageText.text property bool enabled: true property bool busy: state == "busy" @@ -28,7 +30,8 @@ RowLayout onClicked: { - busyMessageText.text = manageButton.busyPrimaryText + busyMessage.text = manageButton.busyPrimaryText + confirmedMessage.text = manageButton.confirmedPrimaryText manageButton.clicked(true) } } @@ -41,7 +44,8 @@ RowLayout onClicked: { - busyMessageText.text = manageButton.busySecondaryText + busyMessage.text = manageButton.busySecondaryText + confirmedMessage.text = manageButton.confirmedSecondaryText manageButton.clicked(false) } } @@ -50,6 +54,7 @@ RowLayout { id: busyMessage visible: false + property alias text: busyMessageText.text height: UM.Theme.getSize("action_button").height width: childrenRect.width @@ -90,6 +95,26 @@ RowLayout } } + Item + { + id: confirmedMessage + property alias text: confirmedMessageText.text + + visible: false + height: UM.Theme.getSize("action_button").height + width: childrenRect.width + + Label + { + id: confirmedMessageText + visible: parent.visble + anchors.verticalCenter: parent.verticalCenter + + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("primary") + } + } + states: [ State @@ -110,6 +135,11 @@ RowLayout target: busyMessage visible: false } + PropertyChanges + { + target: confirmedMessage + visible: false + } }, State { @@ -129,6 +159,11 @@ RowLayout target: busyMessage visible: false } + PropertyChanges + { + target: confirmedMessage + visible: false + } }, State { @@ -157,6 +192,35 @@ RowLayout target: busyMessage visible: manageButton.visible } + PropertyChanges + { + target: confirmedMessage + visible: false + } + }, + State + { + name: "confirmed" + PropertyChanges + { + target: primaryButton + visible: false + } + PropertyChanges + { + target: secondaryButton + visible: false + } + PropertyChanges + { + target: busyMessage + visible: false + } + PropertyChanges + { + target: confirmedMessage + visible: manageButton.visible + } } ] } diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 326cf0583f..46e9214284 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -326,9 +326,11 @@ Rectangle state: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") - busyPrimaryText: catalog.i18nc("@button", "Inabling...") + busyPrimaryText: catalog.i18nc("@button", "Enabling...") + confirmedPrimaryText: catalog.i18nc("@button", "Enabled") secondaryText: catalog.i18nc("@button", "Disable") busySecondaryText: catalog.i18nc("@button", "Disabling...") + confirmedSecondaryText: catalog.i18nc("@button", "Disabled") enabled: !(installManageButton.busy || updateManageButton.busy) visible: root.manageableInListView || root.expanded @@ -351,10 +353,12 @@ Rectangle Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") busyPrimaryText: catalog.i18nc("@button", "Installing...") + confirmedPrimaryText: catalog.i18nc("@button", "Installed") secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "Uninstalling...") + confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") enabled: !(enableManageButton.busy || updateManageButton.busy) - visible: root.manageableInListView || root.expanded + visible: state == "confirmed" || root.manageableInListView || root.expanded onClicked: { @@ -375,7 +379,8 @@ Rectangle state: packageData.stateManageUpdateButton Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Update") - busyPrimaryText: catalog.i18nc("@button", "updating...") + busyPrimaryText: catalog.i18nc("@button", "Updating...") + confirmedPrimaryText: catalog.i18nc("@button", "Updated") enabled: !(installManageButton.busy || enableManageButton.busy) visible: root.manageableInListView || root.expanded -- cgit v1.2.3 From 5b3e9079edfe7ef6722e7c3a17d9eb0fbf410958 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 12:46:45 +0100 Subject: Don't show en-/disable button when un-/installed Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 3 ++- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 67532bc9aa..29b8ff22a5 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -19,6 +19,7 @@ RowLayout property string confirmedSecondaryText: confirmedMessageText.text property bool enabled: true property bool busy: state == "busy" + property bool confirmed: state == "confirmed" signal clicked(bool primary_action) @@ -107,7 +108,7 @@ RowLayout Label { id: confirmedMessageText - visible: parent.visble + visible: parent.visible anchors.verticalCenter: parent.verticalCenter font: UM.Theme.getFont("medium_bold") diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 46e9214284..d782d43cbb 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -332,7 +332,7 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Disabling...") confirmedSecondaryText: catalog.i18nc("@button", "Disabled") enabled: !(installManageButton.busy || updateManageButton.busy) - visible: root.manageableInListView || root.expanded + visible: (root.manageableInListView || root.expanded) && !installManageButton.confirmed onClicked: { if (primary_action) -- cgit v1.2.3 From bb9696c39faf93197f1c4a6ca141437ea8789607 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 15:06:46 +0100 Subject: Reset button if user declines license Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 28 ++++++------- plugins/Marketplace/PackageModel.py | 49 +++++++++++++--------- plugins/Marketplace/resources/qml/ManageButton.qml | 34 ++++++++++++--- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 47b5e8ff4b..c83c5a7130 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 +from .PackageModel import PackageModel, ManageState from .Constants import USER_PACKAGES_URL if TYPE_CHECKING: @@ -161,7 +161,7 @@ class PackageList(ListModel): dialog.deleteLater() # reset package card package = self.getPackageModel(package_id) - package.is_installing = False + package.is_installing = ManageState.FAILED def _requestInstall(self, package_id: str, update: bool = False) -> None: Logger.debug(f"Request installing {package_id}") @@ -185,9 +185,9 @@ class PackageList(ListModel): if package.can_update and to_be_installed: package.can_update = False if update: - package.is_updating = False + package.is_updating = ManageState.HALTED else: - package.is_installing = False + package.is_installing = ManageState.HALTED self.subscribeUserToPackage(package_id, str(package.sdk_version)) def download(self, package_id: str, url: str, update: bool = False) -> None: @@ -232,9 +232,9 @@ class PackageList(ListModel): Logger.error(f"Failed to download package: {package_id} due to {reply_string}") package = self.getPackageModel(package_id) if update: - package.is_updating = False + package.is_updating = ManageState.FAILED else: - package.is_installing = False + package.is_installing = ManageState.FAILED def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: """Subscribe the user (if logged in) to the package for a given SDK @@ -275,7 +275,7 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_installing = True + package.is_installing = ManageState.PROCESSING url = package.download_url Logger.debug(f"Trying to download and install {package_id} from {url}") self.download(package_id, url, False) @@ -288,10 +288,10 @@ class PackageList(ListModel): """ Logger.debug(f"Uninstalling {package_id}") package = self.getPackageModel(package_id) - package.is_installing = True + package.is_installing = ManageState.PROCESSING self._manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) - package.is_installing = False + package.is_installing = ManageState.HALTED @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: @@ -300,7 +300,7 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_updating = True + package.is_updating = ManageState.PROCESSING self._manager.removePackage(package_id, force_add = True) url = package.download_url Logger.debug(f"Trying to download and update {package_id} from {url}") @@ -313,11 +313,11 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_enabling = True + package.is_enabling = ManageState.PROCESSING Logger.debug(f"Enabling {package_id}") self._plugin_registry.enablePlugin(package_id) package.is_active = True - package.is_enabling = False + package.is_enabling = ManageState.HALTED @pyqtSlot(str) def disablePackage(self, package_id: str) -> None: @@ -326,8 +326,8 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_enabling = True + package.is_enabling = ManageState.PROCESSING Logger.debug(f"Disabling {package_id}") self._plugin_registry.disablePlugin(package_id) package.is_active = False - package.is_enabling = False + package.is_enabling = ManageState.HALTED diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 6b313eb9a5..97e57cf951 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -1,16 +1,25 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal import re +from enum import Enum from typing import Any, Dict, List, Optional +from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal + from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. +from UM.Logger import Logger 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. @@ -60,14 +69,14 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") - self._is_installing = False + self._is_installing: ManageState = ManageState.HALTED self._is_recently_installed = False self._is_recently_updated = False self._is_recently_enabled = False self._can_update = False - self._is_updating = False - self._is_enabling = False + self._is_updating: ManageState = ManageState.HALTED + self._is_enabling: ManageState = ManageState.HALTED self._can_downgrade = False self._section_title = section_title self.sdk_version = package_data.get("sdk_version_semver", "") @@ -288,7 +297,7 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageEnableButton(self) -> str: """The state of the manage Enable Button of this package""" - if self._is_enabling: + if self._is_enabling == ManageState.PROCESSING: return "busy" if self._is_recently_enabled: return "confirmed" @@ -299,16 +308,16 @@ class PackageModel(QObject): return "primary" @property - def is_enabling(self) -> bool: + 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: bool) -> None: + def is_enabling(self, value: ManageState) -> None: if value != self._is_enabling: - if not value: - self._is_recently_enabled = True self._is_enabling = value + if value == ManageState.HALTED: + self._is_recently_enabled = True self.stateManageButtonChanged.emit() @property @@ -327,7 +336,7 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageInstallButton(self) -> str: """The state of the Manage Install package card""" - if self._is_installing: + if self._is_installing == ManageState.PROCESSING: return "busy" if self._is_recently_installed: return "confirmed" @@ -340,16 +349,16 @@ class PackageModel(QObject): return "primary" @property - def is_installing(self) -> bool: - """Flag is we're currently installing""" + 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: bool) -> None: + def is_installing(self, value: ManageState) -> None: if value != self._is_installing: - if not value: - self._is_recently_installed = True self._is_installing = value + if value == ManageState.HALTED: + self._is_recently_installed = True self.stateManageButtonChanged.emit() @property @@ -368,7 +377,7 @@ class PackageModel(QObject): @pyqtProperty(str, notify = stateManageButtonChanged) def stateManageUpdateButton(self) -> str: """The state of the manage Update button for this card """ - if self._is_updating: + if self._is_updating == ManageState.PROCESSING: return "busy" if self._is_recently_updated: return "confirmed" @@ -377,16 +386,16 @@ class PackageModel(QObject): return "hidden" @property - def is_updating(self) -> bool: + 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: bool) -> None: + def is_updating(self, value: ManageState) -> None: if value != self._is_updating: - if not value: - self._is_recently_updated = True self._is_updating = value + if value == ManageState.HALTED: + self._is_recently_updated = True self.stateManageButtonChanged.emit() @property diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 29b8ff22a5..0b3008461b 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -18,8 +18,8 @@ RowLayout property string confirmedPrimaryText: confirmedMessageText.text property string confirmedSecondaryText: confirmedMessageText.text property bool enabled: true - property bool busy: state == "busy" - property bool confirmed: state == "confirmed" + property bool busy: false + property bool confirmed: false signal clicked(bool primary_action) @@ -62,7 +62,7 @@ RowLayout UM.RecolorImage { id: busyIndicator - visible: parent.visible + visible: busyMessage.visible width: height anchors.left: parent.left anchors.top: parent.top @@ -76,7 +76,7 @@ RowLayout RotationAnimator { target: busyIndicator - running: busyIndicator.visible + running: busyMessage.visible from: 0 to: 360 loops: Animation.Infinite @@ -86,7 +86,7 @@ RowLayout Label { id: busyMessageText - visible: parent.visible + visible: busyMessage.visible anchors.left: busyIndicator.right anchors.leftMargin: UM.Theme.getSize("narrow_margin").width anchors.verticalCenter: parent.verticalCenter @@ -122,6 +122,12 @@ RowLayout { name: "primary" PropertyChanges + { + target: manageButton + busy: false + confirmed: false + } + PropertyChanges { target: primaryButton visible: true @@ -146,6 +152,12 @@ RowLayout { name: "secondary" PropertyChanges + { + target: manageButton + busy: false + confirmed: false + } + PropertyChanges { target: primaryButton visible: false @@ -179,6 +191,12 @@ RowLayout { name: "busy" PropertyChanges + { + target: manageButton + busy: true + confirmed: false + } + PropertyChanges { target: primaryButton visible: false @@ -203,6 +221,12 @@ RowLayout { name: "confirmed" PropertyChanges + { + target: manageButton + busy: false + confirmed: true + } + PropertyChanges { target: primaryButton visible: false diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index d782d43cbb..721a152b12 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -358,7 +358,7 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Uninstalling...") confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") enabled: !(enableManageButton.busy || updateManageButton.busy) - visible: state == "confirmed" || root.manageableInListView || root.expanded + visible: installManageButton.confirmed || root.manageableInListView || root.expanded onClicked: { -- cgit v1.2.3 From ea4ec5ca2714a426ae1e7b2ed31a69d329e10128 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 15:29:05 +0100 Subject: Removed old debug logger messages Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 14 -------------- plugins/Marketplace/PackageModel.py | 1 - 2 files changed, 15 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index c83c5a7130..62e54e783c 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -125,8 +125,6 @@ class PackageList(ListModel): canInstallChanged = pyqtSignal(str, bool) def _openLicenseDialog(self, package_id: str, license_content: str) -> None: - Logger.debug(f"Prompting license for {package_id}") - plugin_path = self._plugin_registry.getPluginPath("Marketplace") if plugin_path is None: plugin_path = os.path.dirname(__file__) @@ -144,7 +142,6 @@ class PackageList(ListModel): @pyqtSlot(str) def onLicenseAccepted(self, package_id: str) -> None: - Logger.debug(f"Accepted license for {package_id}") # close dialog dialog = self._license_dialogs.pop(package_id) if dialog is not None: @@ -154,7 +151,6 @@ class PackageList(ListModel): @pyqtSlot(str) def onLicenseDeclined(self, package_id: str) -> None: - Logger.debug(f"Declined license for {package_id}") # close dialog dialog = self._license_dialogs.pop(package_id) if dialog is not None: @@ -164,8 +160,6 @@ class PackageList(ListModel): package.is_installing = ManageState.FAILED def _requestInstall(self, package_id: str, update: bool = False) -> None: - Logger.debug(f"Request installing {package_id}") - package_path = self._to_install[package_id] license_content = self._manager.getPackageLicense(package_path) @@ -178,7 +172,6 @@ class PackageList(ListModel): self._install(package_id, update) def _install(self, package_id: str, update: bool = False) -> None: - Logger.debug(f"Installing {package_id}") package_path = self._to_install.pop(package_id) to_be_installed = self._manager.installPackage(package_path) is not None package = self.getPackageModel(package_id) @@ -243,7 +236,6 @@ class PackageList(ListModel): :param sdk_version: the SDK version """ if self._account.isLoggedIn: - Logger.debug(f"Subscribing the user for package: {package_id}") HttpRequestManager.getInstance().put( url = USER_PACKAGES_URL, data = json.dumps({"data": {"package_id": package_id, "sdk_version": sdk_version}}).encode(), @@ -256,7 +248,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ if self._account.isLoggedIn: - Logger.debug(f"Unsubscribing the user for package: {package_id}") HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope) # --- Handle the manage package buttons --- @@ -277,7 +268,6 @@ class PackageList(ListModel): package = self.getPackageModel(package_id) package.is_installing = ManageState.PROCESSING url = package.download_url - Logger.debug(f"Trying to download and install {package_id} from {url}") self.download(package_id, url, False) @pyqtSlot(str) @@ -286,7 +276,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ - Logger.debug(f"Uninstalling {package_id}") package = self.getPackageModel(package_id) package.is_installing = ManageState.PROCESSING self._manager.removePackage(package_id) @@ -303,7 +292,6 @@ class PackageList(ListModel): package.is_updating = ManageState.PROCESSING self._manager.removePackage(package_id, force_add = True) url = package.download_url - Logger.debug(f"Trying to download and update {package_id} from {url}") self.download(package_id, url, True) @pyqtSlot(str) @@ -314,7 +302,6 @@ class PackageList(ListModel): """ package = self.getPackageModel(package_id) package.is_enabling = ManageState.PROCESSING - Logger.debug(f"Enabling {package_id}") self._plugin_registry.enablePlugin(package_id) package.is_active = True package.is_enabling = ManageState.HALTED @@ -327,7 +314,6 @@ class PackageList(ListModel): """ package = self.getPackageModel(package_id) package.is_enabling = ManageState.PROCESSING - Logger.debug(f"Disabling {package_id}") 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 97e57cf951..4e7fce5ce4 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -9,7 +9,6 @@ from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. -from UM.Logger import Logger catalog = i18nCatalog("cura") -- cgit v1.2.3 From dae92c354cb3dc0ef952a98c807411195347ed41 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 15:32:36 +0100 Subject: Marketplace doesn't need to inherit from QObject Review comment Contributes to: CURA-8587 --- plugins/Marketplace/Marketplace.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index d55e538f6c..b47dae0a4a 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. import os.path -from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtCore import pyqtSlot from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING @@ -18,15 +18,14 @@ if TYPE_CHECKING: from PyQt5.QtCore import QObject -class Marketplace(Extension, QObject): +class Marketplace(Extension): """ The main managing object for the Marketplace plug-in. """ - def __init__(self, parent: Optional[QObject] = None) -> None: - QObject.__init__(self, parent = parent) - Extension.__init__(self) - self._window: Optional[QObject] = None # If the window has been loaded yet, it'll be cached in here. + def __init__(self) -> None: + super().__init__() + self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. self.plugin_registry: Optional[PluginRegistry] = None qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") -- cgit v1.2.3 From 013e0b51e9b7266a4f570fdb9776b0093f4dc53d Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 15:56:35 +0100 Subject: Storing multiple ongoing_requests A bit of defensive programming Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 4 +++- plugins/Marketplace/PackageList.py | 8 ++++---- plugins/Marketplace/RemotePackageList.py | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 196f3f19c6..ebda51b4fe 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -38,6 +38,7 @@ class LocalPackageList(PackageList): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._has_footer = False + self._ongoing_requests["check_updates"] = None @pyqtSlot() def updatePackages(self) -> None: @@ -74,7 +75,7 @@ class LocalPackageList(PackageList): 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( + self._ongoing_requests["check_updates"] = HttpRequestManager.getInstance().get( request_url, scope = self._scope, callback = self._parseResponse @@ -100,3 +101,4 @@ class LocalPackageList(PackageList): package.can_update = True self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) + self._ongoing_requests["check_updates"] = None diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 62e54e783c..a2c67dc1ef 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -5,7 +5,7 @@ import json import os.path from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import cast, Dict, Optional, Set, TYPE_CHECKING +from typing import cast, Dict, List, Optional, Set, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel @@ -49,7 +49,7 @@ class PackageList(ListModel): self.canInstallChanged.connect(self._requestInstall) self._local_packages: Set[str] = {p["package_id"] for p in self._manager.local_packages} - self._ongoing_request: Optional[HttpRequestData] = None + self._ongoing_requests: Dict[str, Optional[HttpRequestData]] = {"download_package": None} self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._license_dialogs: Dict[str, QObject] = {} @@ -197,7 +197,7 @@ class PackageList(ListModel): def downloadError(reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None: self._downloadError(package_id, update, reply, error) - HttpRequestManager.getInstance().get( + self._ongoing_requests["download_package"] = HttpRequestManager.getInstance().get( url, scope = self._scope, callback = downloadFinished, @@ -211,8 +211,8 @@ class PackageList(ListModel): while bytes_read: temp_file.write(bytes_read) bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) - Logger.debug(f"Finished downloading {package_id} and stored it as {temp_file.name}") self._to_install[package_id] = temp_file.name + self._ongoing_requests["download_package"] = None self.canInstallChanged.emit(package_id, update) except IOError as e: Logger.error(f"Failed to write downloaded package to temp file {e}") diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 88e1c28045..13f3d33d93 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -29,6 +29,7 @@ class RemotePackageList(PackageList): self._requested_search_string = "" self._current_search_string = "" self._request_url = self._initialRequestUrl() + self._ongoing_requests["get_packages"] = None self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() @@ -49,7 +50,7 @@ class RemotePackageList(PackageList): self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) - self._ongoing_request = HttpRequestManager.getInstance().get( + self._ongoing_requests["get_packages"] = HttpRequestManager.getInstance().get( self._request_url, scope = self._scope, callback = self._parseResponse, @@ -58,8 +59,8 @@ class RemotePackageList(PackageList): @pyqtSlot() def abortUpdating(self) -> None: - HttpRequestManager.getInstance().abortRequest(self._ongoing_request) - self._ongoing_request = None + HttpRequestManager.getInstance().abortRequest(self._ongoing_requests["get_packages"]) + self._ongoing_requests["get_packages"] = None def reset(self) -> None: self.clear() -- cgit v1.2.3 From d2a9d7d94d1d4ffb0b880693fcb8bb160ef7195d Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 7 Dec 2021 16:02:54 +0100 Subject: Get 'already going to be installed' status in constructor. Otherwise this isn't saved, and the state of 'installed, but needs restart' (as shown in the package card) won't be known to the package card (buttons), resulting in an 'Install' button when tabs are switched. part of CURA-8587 --- plugins/Marketplace/PackageModel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 4e7fce5ce4..ed64a85c04 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal +from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. @@ -69,7 +70,7 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing: ManageState = ManageState.HALTED - self._is_recently_installed = False + self._is_recently_installed = self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() self._is_recently_updated = False self._is_recently_enabled = False -- cgit v1.2.3 From 14bc196154edd03802ab2ae84a6c9ac6bdf42115 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 16:08:51 +0100 Subject: Hidden other manage buttons when the other is confirmed Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 721a152b12..12f24f6f6f 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -332,7 +332,7 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Disabling...") confirmedSecondaryText: catalog.i18nc("@button", "Disabled") enabled: !(installManageButton.busy || updateManageButton.busy) - visible: (root.manageableInListView || root.expanded) && !installManageButton.confirmed + visible: (root.manageableInListView || root.expanded) && !(installManageButton.confirmed || updateManageButton.confirmed) onClicked: { if (primary_action) @@ -358,7 +358,7 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Uninstalling...") confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") enabled: !(enableManageButton.busy || updateManageButton.busy) - visible: installManageButton.confirmed || root.manageableInListView || root.expanded + visible: (installManageButton.confirmed || root.manageableInListView || root.expanded) && !(updateManageButton.confirmed || enableManageButton.confirmed) onClicked: { @@ -382,7 +382,7 @@ Rectangle busyPrimaryText: catalog.i18nc("@button", "Updating...") confirmedPrimaryText: catalog.i18nc("@button", "Updated") enabled: !(installManageButton.busy || enableManageButton.busy) - visible: root.manageableInListView || root.expanded + visible: (root.manageableInListView || root.expanded) && !installManageButton.confirmed onClicked: packageData.updatePackageTriggered(packageData.packageId) } -- cgit v1.2.3 From 9e4258ef8b3d625178f34496e06dcf6f7eff4275 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 16:22:31 +0100 Subject: Set the is_recently_installed flag Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 6 +++--- plugins/Marketplace/PackageModel.py | 13 +++++++++++-- plugins/Marketplace/RemotePackageList.py | 5 ++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index ebda51b4fe..c3491fc3af 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -65,10 +65,10 @@ class LocalPackageList(PackageList): 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_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) + package.can_downgrade = self._manager.canDowngrade(package_id) + if package_id in self._manager.getPackagesToRemove() or package_id in self._manager.getPackagesToInstall(): + package.is_recently_installed = True return package def checkForUpdates(self, packages: List[Dict[str, Any]]): diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index ed64a85c04..55e8aa3a75 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -7,7 +7,6 @@ from typing import Any, Dict, List, Optional from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal -from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. @@ -70,7 +69,7 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing: ManageState = ManageState.HALTED - self._is_recently_installed = self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() + self._is_recently_installed = False self._is_recently_updated = False self._is_recently_enabled = False @@ -361,6 +360,16 @@ class PackageModel(QObject): self._is_recently_installed = True self.stateManageButtonChanged.emit() + @property + def is_recently_installed(self): + return self._is_recently_installed + + @is_recently_installed.setter + def is_recently_installed(self, value): + if value != self._is_recently_installed: + value = self._is_recently_installed + self.stateManageButtonChanged.emit() + @property def can_downgrade(self) -> bool: """Flag if the installed package can be downgraded to a bundled version""" diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 13f3d33d93..583a427c44 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -129,11 +129,14 @@ class RemotePackageList(PackageList): return for package_data in response_data["data"]: - if package_data["package_id"] in self._local_packages: + package_id = package_data["package_id"] + if package_id in self._local_packages: continue # We should only show packages which are not already installed 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.is_recently_installed = 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 -- cgit v1.2.3 From 3be6747e5dc9d0129878e9fe57556bb8be8df53a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 7 Dec 2021 18:25:46 +0100 Subject: Trying to set a persistent install managebutton Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 2 +- plugins/Marketplace/resources/qml/ManageButton.qml | 57 ++++++++-------------- plugins/Marketplace/resources/qml/PackageCard.qml | 20 +++++--- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 55e8aa3a75..99d0039701 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -367,7 +367,7 @@ class PackageModel(QObject): @is_recently_installed.setter def is_recently_installed(self, value): if value != self._is_recently_installed: - value = self._is_recently_installed + self._is_recently_installed = value self.stateManageButtonChanged.emit() @property diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 0b3008461b..a423e90ff6 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -17,16 +17,14 @@ RowLayout property string busySecondaryText: busyMessageText.text property string confirmedPrimaryText: confirmedMessageText.text property string confirmedSecondaryText: confirmedMessageText.text - property bool enabled: true - property bool busy: false - property bool confirmed: false + property bool busy + property bool confirmed signal clicked(bool primary_action) Cura.PrimaryButton { id: primaryButton - visible: false enabled: manageButton.enabled onClicked: @@ -40,7 +38,6 @@ RowLayout Cura.SecondaryButton { id: secondaryButton - visible: false enabled: manageButton.enabled onClicked: @@ -54,7 +51,6 @@ RowLayout Item { id: busyMessage - visible: false property alias text: busyMessageText.text height: UM.Theme.getSize("action_button").height width: childrenRect.width @@ -62,7 +58,6 @@ RowLayout UM.RecolorImage { id: busyIndicator - visible: busyMessage.visible width: height anchors.left: parent.left anchors.top: parent.top @@ -86,7 +81,6 @@ RowLayout Label { id: busyMessageText - visible: busyMessage.visible anchors.left: busyIndicator.right anchors.leftMargin: UM.Theme.getSize("narrow_margin").width anchors.verticalCenter: parent.verticalCenter @@ -101,14 +95,12 @@ RowLayout id: confirmedMessage property alias text: confirmedMessageText.text - visible: false height: UM.Theme.getSize("action_button").height width: childrenRect.width Label { id: confirmedMessageText - visible: parent.visible anchors.verticalCenter: parent.verticalCenter font: UM.Theme.getFont("medium_bold") @@ -122,12 +114,6 @@ RowLayout { name: "primary" PropertyChanges - { - target: manageButton - busy: false - confirmed: false - } - PropertyChanges { target: primaryButton visible: true @@ -152,12 +138,6 @@ RowLayout { name: "secondary" PropertyChanges - { - target: manageButton - busy: false - confirmed: false - } - PropertyChanges { target: primaryButton visible: false @@ -183,7 +163,22 @@ RowLayout name: "hidden" PropertyChanges { - target: manageButton + target: primaryButton + visible: false + } + PropertyChanges + { + target: secondaryButton + visible: false + } + PropertyChanges + { + target: busyMessage + visible: false + } + PropertyChanges + { + target: confirmedMessage visible: false } }, @@ -191,12 +186,6 @@ RowLayout { name: "busy" PropertyChanges - { - target: manageButton - busy: true - confirmed: false - } - PropertyChanges { target: primaryButton visible: false @@ -209,7 +198,7 @@ RowLayout PropertyChanges { target: busyMessage - visible: manageButton.visible + visible: true } PropertyChanges { @@ -221,12 +210,6 @@ RowLayout { name: "confirmed" PropertyChanges - { - target: manageButton - busy: false - confirmed: true - } - PropertyChanges { target: primaryButton visible: false @@ -244,7 +227,7 @@ RowLayout PropertyChanges { target: confirmedMessage - visible: manageButton.visible + visible: true } } ] diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 12f24f6f6f..667c645c61 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -323,7 +323,9 @@ Rectangle ManageButton { id: enableManageButton - state: packageData.stateManageEnableButton + state: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" + busy: packageData.enableManageButton == "busy" + confirmed: packageData.enableManageButton == "confirmed" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") busyPrimaryText: catalog.i18nc("@button", "Enabling...") @@ -332,9 +334,9 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Disabling...") confirmedSecondaryText: catalog.i18nc("@button", "Disabled") enabled: !(installManageButton.busy || updateManageButton.busy) - visible: (root.manageableInListView || root.expanded) && !(installManageButton.confirmed || updateManageButton.confirmed) - onClicked: { + onClicked: + { if (primary_action) { packageData.enablePackageTriggered(packageData.packageId) @@ -349,7 +351,9 @@ Rectangle ManageButton { id: installManageButton - state: packageData.stateManageInstallButton + state: (root.manageableInListView || root.expanded || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" + busy: packageData.stateManageInstallButton == "busy" + confirmed: packageData.stateManageInstallButton == "confirmed" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") busyPrimaryText: catalog.i18nc("@button", "Installing...") @@ -358,7 +362,8 @@ Rectangle busySecondaryText: catalog.i18nc("@button", "Uninstalling...") confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") enabled: !(enableManageButton.busy || updateManageButton.busy) - visible: (installManageButton.confirmed || root.manageableInListView || root.expanded) && !(updateManageButton.confirmed || enableManageButton.confirmed) + + onStateChanged: print(packageData.displayName + " " + state) // TODO: Cleanup once you find out why this happens onClicked: { @@ -376,13 +381,14 @@ Rectangle ManageButton { id: updateManageButton - state: packageData.stateManageUpdateButton + state: (root.manageableInListView || root.expanded) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" + busy: packageData.stateManageUpdateButton == "busy" + confirmed: packageData.stateManageUpdateButton == "confirmed" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "Updating...") confirmedPrimaryText: catalog.i18nc("@button", "Updated") enabled: !(installManageButton.busy || enableManageButton.busy) - visible: (root.manageableInListView || root.expanded) && !installManageButton.confirmed onClicked: packageData.updatePackageTriggered(packageData.packageId) } -- cgit v1.2.3 From a61c3e9eff66eae187101f559e96aa264bef2c17 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 7 Dec 2021 21:23:54 +0100 Subject: Peristance of 'Installed' text. part of CURA-8587 --- plugins/Marketplace/PackageModel.py | 6 ++++++ plugins/Marketplace/resources/qml/ManageButton.qml | 2 ++ plugins/Marketplace/resources/qml/PackageCard.qml | 3 +-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 99d0039701..382a9fc881 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -291,6 +291,8 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) + recentlyInstalledChanged = pyqtSignal(bool) + # --- enabling --- @pyqtProperty(str, notify = stateManageButtonChanged) @@ -370,6 +372,10 @@ class PackageModel(QObject): self._is_recently_installed = value self.stateManageButtonChanged.emit() + @pyqtProperty(bool, notify = stateManageButtonChanged) + def isRecentlyInstalled(self): + return self._is_recently_installed + @property def can_downgrade(self) -> bool: """Flag if the installed package can be downgraded to a bundled version""" diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index a423e90ff6..b2a3ec7f1b 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -19,6 +19,7 @@ RowLayout property string confirmedSecondaryText: confirmedMessageText.text property bool busy property bool confirmed + property bool confirmedTextChoice: true signal clicked(bool primary_action) @@ -228,6 +229,7 @@ RowLayout { target: confirmedMessage visible: true + text: manageButton.confirmedTextChoice ? manageButton.confirmedPrimaryText : manageButton.confirmedSecondaryText } } ] diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 667c645c61..b43ea580ba 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -361,10 +361,9 @@ Rectangle secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "Uninstalling...") confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") + confirmedTextChoice: packageData.isRecentlyInstalled enabled: !(enableManageButton.busy || updateManageButton.busy) - onStateChanged: print(packageData.displayName + " " + state) // TODO: Cleanup once you find out why this happens - onClicked: { if (primary_action) -- cgit v1.2.3 From 6c976bc9b0d989777be3ba9d65896fb92b8144dd Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 08:06:56 +0100 Subject: Introduced a Manager to centralize plugin/package management Should have done this from the start. Will move other relevant scattered functions to this type. For now it checks if the restart banner needs to show. Taking into account that a user can toggle between enable and disable without an actual restart. Even with multiple plugins. Contributes to: CURA-8587 --- plugins/Marketplace/Manager.py | 32 +++++++++++++++++++++++ plugins/Marketplace/Marketplace.py | 4 ++- plugins/Marketplace/PackageModel.py | 2 -- plugins/Marketplace/resources/qml/Marketplace.qml | 4 ++- 4 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 plugins/Marketplace/Manager.py diff --git a/plugins/Marketplace/Manager.py b/plugins/Marketplace/Manager.py new file mode 100644 index 0000000000..f367579079 --- /dev/null +++ b/plugins/Marketplace/Manager.py @@ -0,0 +1,32 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal + +from cura.CuraApplication import CuraApplication +from UM.PluginRegistry import PluginRegistry + +class Manager(QObject): + def __init__(self, parent: Optional[QObject] = None): + super().__init__(parent = parent) + self._manager: "CuraPackageManager" = CuraApplication.getInstance().getPackageManager() + self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() + + self._manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) + self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) + + self._restart_needed = False + + def checkIfRestartNeeded(self): + if self._manager.hasPackagesToRemoveOrInstall or len(self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: + self._restart_needed = True + else: + self._restart_needed = False + self.showRestartNotificationChanged.emit() + + showRestartNotificationChanged = pyqtSignal() + + @pyqtProperty(bool, notify = showRestartNotificationChanged) + def showRestartNotification(self) -> bool: + return self._restart_needed diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index b47dae0a4a..858ea867ee 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -13,6 +13,7 @@ from UM.PluginRegistry import PluginRegistry # To find out where we are stored from .RemotePackageList import RemotePackageList # To register this type with QML. from .LocalPackageList import LocalPackageList # To register this type with QML. +from .Manager import Manager # To register this type with QML. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -30,6 +31,7 @@ class Marketplace(Extension): qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") + qmlRegisterType(Manager, "Marketplace", 1, 0, "Manager") @pyqtSlot() def show(self) -> None: @@ -44,7 +46,7 @@ class Marketplace(Extension): if plugin_path is None: plugin_path = os.path.dirname(__file__) path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {"plugin_registry": self.plugin_registry}) + self._window = CuraApplication.getInstance().createQmlComponent(path, {}) if self._window is None: # Still None? Failed to load the QML then. return self._window.show() diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 382a9fc881..c17769d6ef 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -300,8 +300,6 @@ class PackageModel(QObject): """The state of the manage Enable Button of this package""" if self._is_enabling == ManageState.PROCESSING: return "busy" - if self._is_recently_enabled: - return "confirmed" if self._package_type == "material" or not self._is_installed: return "hidden" if self._is_installed and self._is_active: diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 5c56b0c41d..c04ef5c027 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -8,11 +8,13 @@ import QtQuick.Window 2.2 import UM 1.2 as UM import Cura 1.6 as Cura +import Marketplace 1.0 as Marketplace Window { id: marketplaceDialog property variant catalog: UM.I18nCatalog { name: "cura" } + property variant manager: Marketplace.Manager { } signal searchStringChanged(string new_search) @@ -232,7 +234,7 @@ Window { height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("primary") - visible: CuraApplication.getPackageManager().hasPackagesToRemoveOrInstall || plugin_registry.hasPluginsEnabledOrDisabled + visible: manager.showRestartNotification anchors { left: parent.left -- cgit v1.2.3 From 453de95d12d93dcbf013dbaa2677590521064cc1 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 09:58:53 +0100 Subject: Defensive programming Long API calls might return after the Local or Remote PackageList has been deconstructed. Somehow setting the ownership in QML doesn't seem to work for this. So we guard against this with a try catch block. Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 20 +++++++++++++------- plugins/Marketplace/PackageList.py | 5 +++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index c3491fc3af..6fb9cfbf34 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -95,10 +95,16 @@ class LocalPackageList(PackageList): if len(response_data["data"]) == 0: return - for package_data in response_data["data"]: - package = self.getPackageModel(package_data["package_id"]) - package.download_url = package_data.get("download_url", "") - package.can_update = True - - self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) - self._ongoing_requests["check_updates"] = None + try: + for package_data in response_data["data"]: + package = self.getPackageModel(package_data["package_id"]) + package.download_url = package_data.get("download_url", "") + package.can_update = True + + self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) + self._ongoing_requests["check_updates"] = None + 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 RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object + # was deleted when it was still parsing the response + return diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index a2c67dc1ef..7e64373e9c 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -218,6 +218,11 @@ class PackageList(ListModel): Logger.error(f"Failed to write downloaded package to temp file {e}") temp_file.close() self._downloadError(package_id, update) + 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 + # was deleted when it was still parsing the response + return def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if reply: -- cgit v1.2.3 From 7be2da587b9e37b0171d644543c77f3ab63a4e21 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 10:18:08 +0100 Subject: Automatic abortRequest for each API request Defensive programming Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 23 ++++++++++++++++------- plugins/Marketplace/RemotePackageList.py | 12 ------------ plugins/Marketplace/resources/qml/Packages.qml | 2 +- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 7e64373e9c..e3af31d81a 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -5,7 +5,7 @@ import json import os.path from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt -from typing import cast, Dict, List, Optional, Set, TYPE_CHECKING +from typing import cast, Dict, Optional, Set, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel @@ -53,15 +53,24 @@ class PackageList(ListModel): self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._license_dialogs: Dict[str, QObject] = {} + def __del__(self) -> None: + """ When this object is deleted it will loop through all registered API requests and aborts them """ + self.cleanUpAPIRequest() + + def abortRequest(self, request_id: str) -> None: + """Aborts a single request""" + if request_id in self._ongoing_requests and self._ongoing_requests[request_id]: + HttpRequestManager.getInstance().abortRequest(self._ongoing_requests[request_id]) + self._ongoing_requests[request_id] = None + @pyqtSlot() - def updatePackages(self) -> None: - """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" - pass + def cleanUpAPIRequest(self) -> None: + for request_id in self._ongoing_requests: + self.abortRequest(request_id) @pyqtSlot() - def abortUpdating(self) -> None: - """ A Qt slot which allows the update process to be aborted. Override this for child classes with async/callback - updatePackges methods""" + def updatePackages(self) -> None: + """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" pass def reset(self) -> None: diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 583a427c44..d20cb5f4b0 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -33,13 +33,6 @@ class RemotePackageList(PackageList): self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.emit() - 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.abortUpdating() - @pyqtSlot() def updatePackages(self) -> None: """ @@ -57,11 +50,6 @@ class RemotePackageList(PackageList): error_callback = self._onError ) - @pyqtSlot() - def abortUpdating(self) -> None: - HttpRequestManager.getInstance().abortRequest(self._ongoing_requests["get_packages"]) - self._ongoing_requests["get_packages"] = None - def reset(self) -> None: self.clear() self._request_url = self._initialRequestUrl() diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 62c0f92149..194c90c248 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -24,7 +24,7 @@ ListView clip: true Component.onCompleted: model.updatePackages() - Component.onDestruction: model.abortUpdating() + Component.onDestruction: model.cleanUpAPIRequest() spacing: UM.Theme.getSize("default_margin").height -- cgit v1.2.3 From 5e35e19f219e9500403bd0bd5397e44cbbf91cd8 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 8 Dec 2021 10:53:51 +0100 Subject: Split PackageCard into PackageCard and PackagePage cura 8734 --- plugins/Marketplace/resources/qml/PackageCard.qml | 298 +----------- .../Marketplace/resources/qml/PackageDetails.qml | 7 +- plugins/Marketplace/resources/qml/PackagePage.qml | 511 +++++++++++++++++++++ 3 files changed, 516 insertions(+), 300 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackagePage.qml diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index b43ea580ba..19b6280ddf 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -12,57 +12,12 @@ Rectangle { id: root property var packageData - property bool expanded: false property bool manageableInListView height: childrenRect.height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width - states: - [ - State - { - name: "Folded" - when: !expanded - PropertyChanges - { - target: shortDescription - visible: true - } - PropertyChanges - { - target: downloadCount - visible: false - } - PropertyChanges - { - target: extendedDescription - visible: false - } - }, - State - { - name: "Expanded" - when: expanded - PropertyChanges - { - target: shortDescription - visible: false - } - PropertyChanges - { - target: downloadCount - visible: true - } - PropertyChanges - { - target: extendedDescription - visible: true - } - } - ] - Column { width: parent.width @@ -258,32 +213,6 @@ Rectangle } } - Row - { - id: downloadCount - Layout.preferredWidth: parent.width - Layout.fillHeight: true - - UM.RecolorImage - { - id: downloadsIcon - width: UM.Theme.getSize("card_tiny_icon").width - height: UM.Theme.getSize("card_tiny_icon").height - - source: UM.Theme.getIcon("Download") - color: UM.Theme.getColor("text") - } - - Label - { - anchors.verticalCenter: downloadsIcon.verticalCenter - - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default") - text: packageData.downloadCount - } - } - // Author and action buttons. RowLayout { @@ -351,7 +280,7 @@ Rectangle ManageButton { id: installManageButton - state: (root.manageableInListView || root.expanded || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" + state: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" busy: packageData.stateManageInstallButton == "busy" confirmed: packageData.stateManageInstallButton == "confirmed" Layout.alignment: Qt.AlignTop @@ -380,7 +309,7 @@ Rectangle ManageButton { id: updateManageButton - state: (root.manageableInListView || root.expanded) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" + state: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" busy: packageData.stateManageUpdateButton == "busy" confirmed: packageData.stateManageUpdateButton == "confirmed" Layout.alignment: Qt.AlignTop @@ -394,229 +323,6 @@ Rectangle } } } - - Column - { - id: extendedDescription - width: parent.width - - padding: UM.Theme.getSize("default_margin").width - topPadding: 0 - spacing: UM.Theme.getSize("default_margin").height - - Label - { - width: parent.width - parent.padding * 2 - - text: catalog.i18nc("@header", "Description") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - - Label - { - width: parent.width - parent.padding * 2 - - text: packageData.formattedDescription - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - wrapMode: Text.Wrap - textFormat: Text.RichText - - onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) - } - - Column //Separate column to have no spacing between compatible printers. - { - id: compatiblePrinterColumn - width: parent.width - parent.padding * 2 - - visible: packageData.packageType === "material" - spacing: 0 - - Label - { - width: parent.width - - text: catalog.i18nc("@header", "Compatible printers") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - - Repeater - { - model: packageData.compatiblePrinters - - Label - { - width: compatiblePrinterColumn.width - - text: modelData - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Label - { - width: parent.width - - visible: packageData.compatiblePrinters.length == 0 - text: "(" + catalog.i18nc("@info", "No compatibility information") + ")" - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Column - { - id: compatibleSupportMaterialColumn - width: parent.width - parent.padding * 2 - - visible: packageData.packageType === "material" - spacing: 0 - - Label - { - width: parent.width - - text: catalog.i18nc("@header", "Compatible support materials") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - - Repeater - { - model: packageData.compatibleSupportMaterials - - Label - { - width: compatibleSupportMaterialColumn.width - - text: modelData - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Label - { - width: parent.width - - visible: packageData.compatibleSupportMaterials.length == 0 - text: "(" + catalog.i18nc("@info No materials", "None") + ")" - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Column - { - width: parent.width - parent.padding * 2 - - visible: packageData.packageType === "material" - spacing: 0 - - Label - { - width: parent.width - - text: catalog.i18nc("@header", "Compatible with Material Station") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - - Label - { - width: parent.width - - text: packageData.isCompatibleMaterialStation ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Column - { - width: parent.width - parent.padding * 2 - - visible: packageData.packageType === "material" - spacing: 0 - - Label - { - width: parent.width - - text: catalog.i18nc("@header", "Optimized for Air Manager") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - - Label - { - width: parent.width - - text: packageData.isCompatibleAirManager ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - - Row - { - id: externalButtonRow - anchors.horizontalCenter: parent.horizontalCenter - - spacing: UM.Theme.getSize("narrow_margin").width - - Cura.SecondaryButton - { - text: packageData.packageType === "plugin" ? catalog.i18nc("@button", "Visit plug-in website") : catalog.i18nc("@button", "Website") - iconSource: UM.Theme.getIcon("Globe") - outlineColor: "transparent" - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } - - Cura.SecondaryButton - { - visible: packageData.packageType === "material" - text: catalog.i18nc("@button", "Buy spool") - iconSource: UM.Theme.getIcon("ShoppingCart") - outlineColor: "transparent" - onClicked: Qt.openUrlExternally(packageData.whereToBuy) - } - - Cura.SecondaryButton - { - visible: packageData.packageType === "material" - text: catalog.i18nc("@button", "Safety datasheet") - iconSource: UM.Theme.getIcon("Warning") - outlineColor: "transparent" - onClicked: Qt.openUrlExternally(packageData.safetyDataSheet) - } - - Cura.SecondaryButton - { - visible: packageData.packageType === "material" - text: catalog.i18nc("@button", "Technical datasheet") - iconSource: UM.Theme.getIcon("DocumentFilled") - outlineColor: "transparent" - onClicked: Qt.openUrlExternally(packageData.technicalDataSheet) - } - } - } } FontMetrics diff --git a/plugins/Marketplace/resources/qml/PackageDetails.qml b/plugins/Marketplace/resources/qml/PackageDetails.qml index fdf1c8f92c..2599c7f28c 100644 --- a/plugins/Marketplace/resources/qml/PackageDetails.qml +++ b/plugins/Marketplace/resources/qml/PackageDetails.qml @@ -74,11 +74,11 @@ Item clip: true //Need to clip, not for the bottom (which is off the window) but for the top (which would overlap the header). ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - contentHeight: expandedPackageCard.height + UM.Theme.getSize("default_margin").height * 2 + contentHeight: packagePage.height + UM.Theme.getSize("default_margin").height * 2 - PackageCard + PackagePage { - id: expandedPackageCard + id: packagePage anchors { left: parent.left @@ -90,7 +90,6 @@ Item } packageData: detailPage.packageData - expanded: true } } } diff --git a/plugins/Marketplace/resources/qml/PackagePage.qml b/plugins/Marketplace/resources/qml/PackagePage.qml new file mode 100644 index 0000000000..1669f8ffc7 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackagePage.qml @@ -0,0 +1,511 @@ +// 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 + +Rectangle +{ + id: root + property var packageData + property bool manageableInListView + + height: childrenRect.height + color: UM.Theme.getColor("main_background") + radius: UM.Theme.getSize("default_radius").width + + Column + { + width: parent.width + + spacing: 0 + + Item + { + width: parent.width + height: UM.Theme.getSize("card").height + + Image + { + id: packageItem + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("card_icon").width + height: width + + source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" + } + + ColumnLayout + { + anchors + { + left: packageItem.right + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + top: parent.top + topMargin: UM.Theme.getSize("narrow_margin").height + } + height: packageItem.height + packageItem.anchors.margins * 2 + + // Title row. + RowLayout + { + id: titleBar + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + + Label + { + text: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } + VerifiedIcon + { + enabled: packageData.isCheckedByUltimaker + visible: packageData.isCheckedByUltimaker + } + + + Control + { + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip + { + tooltipText: "" // TODO + visible: parent.hovered + } + + UM.RecolorImage + { + anchors.fill: parent + + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") // TODO + } + + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO + } + + Label + { + id: packageVersionLabel + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true + } + + Button + { + id: externalLinkButton + + // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: UM.Theme.getSize("narrow_margin").width + topPadding: UM.Theme.getSize("narrow_margin").width + bottomPadding: UM.Theme.getSize("narrow_margin").width + + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + contentItem: UM.RecolorImage + { + source: UM.Theme.getIcon("LinkExternal") + color: UM.Theme.getColor("icon") + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + } + + background: Rectangle + { + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" + radius: externalLinkButton.width / 2 + } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } + } + + Row + { + id: downloadCount + Layout.preferredWidth: parent.width + Layout.fillHeight: true + + UM.RecolorImage + { + id: downloadsIcon + width: UM.Theme.getSize("card_tiny_icon").width + height: UM.Theme.getSize("card_tiny_icon").height + + source: UM.Theme.getIcon("Download") + color: UM.Theme.getColor("text") + } + + Label + { + anchors.verticalCenter: downloadsIcon.verticalCenter + + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("default") + text: packageData.downloadCount + } + } + + // Author and action buttons. + RowLayout + { + id: authorAndActionButton + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + + spacing: UM.Theme.getSize("narrow_margin").width + + Label + { + id: authorBy + Layout.alignment: Qt.AlignCenter + + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } + + Cura.TertiaryButton + { + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignCenter + + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } + + ManageButton + { + id: enableManageButton + state: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" + busy: packageData.enableManageButton == "busy" + confirmed: packageData.enableManageButton == "confirmed" + Layout.alignment: Qt.AlignTop + primaryText: catalog.i18nc("@button", "Enable") + busyPrimaryText: catalog.i18nc("@button", "Enabling...") + confirmedPrimaryText: catalog.i18nc("@button", "Enabled") + secondaryText: catalog.i18nc("@button", "Disable") + busySecondaryText: catalog.i18nc("@button", "Disabling...") + confirmedSecondaryText: catalog.i18nc("@button", "Disabled") + enabled: !(installManageButton.busy || updateManageButton.busy) + + onClicked: + { + if (primary_action) + { + packageData.enablePackageTriggered(packageData.packageId) + } + else + { + packageData.disablePackageTriggered(packageData.packageId) + } + } + } + + ManageButton + { + id: installManageButton + state: !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" + busy: packageData.stateManageInstallButton == "busy" + confirmed: packageData.stateManageInstallButton == "confirmed" + Layout.alignment: Qt.AlignTop + primaryText: catalog.i18nc("@button", "Install") + busyPrimaryText: catalog.i18nc("@button", "Installing...") + confirmedPrimaryText: catalog.i18nc("@button", "Installed") + secondaryText: catalog.i18nc("@button", "Uninstall") + busySecondaryText: catalog.i18nc("@button", "Uninstalling...") + confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") + confirmedTextChoice: packageData.isRecentlyInstalled + enabled: !(enableManageButton.busy || updateManageButton.busy) + + onClicked: + { + if (primary_action) + { + packageData.installPackageTriggered(packageData.packageId) + } + else + { + packageData.uninstallPackageTriggered(packageData.packageId) + } + } + } + + ManageButton + { + id: updateManageButton + state: !installManageButton.confirmed || updateManageButton.confirmed ? packageData.stateManageUpdateButton : "hidden" + busy: packageData.stateManageUpdateButton == "busy" + confirmed: packageData.stateManageUpdateButton == "confirmed" + Layout.alignment: Qt.AlignTop + primaryText: catalog.i18nc("@button", "Update") + busyPrimaryText: catalog.i18nc("@button", "Updating...") + confirmedPrimaryText: catalog.i18nc("@button", "Updated") + enabled: !(installManageButton.busy || enableManageButton.busy) + + onClicked: packageData.updatePackageTriggered(packageData.packageId) + } + } + } + } + + Column + { + id: extendedDescription + width: parent.width + + padding: UM.Theme.getSize("default_margin").width + topPadding: 0 + spacing: UM.Theme.getSize("default_margin").height + + Label + { + width: parent.width - parent.padding * 2 + + text: catalog.i18nc("@header", "Description") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width - parent.padding * 2 + + text: packageData.formattedDescription + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + linkColor: UM.Theme.getColor("text_link") + wrapMode: Text.Wrap + textFormat: Text.RichText + + onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) + } + + Column //Separate column to have no spacing between compatible printers. + { + id: compatiblePrinterColumn + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible printers") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Repeater + { + model: packageData.compatiblePrinters + + Label + { + width: compatiblePrinterColumn.width + + text: modelData + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Label + { + width: parent.width + + visible: packageData.compatiblePrinters.length == 0 + text: "(" + catalog.i18nc("@info", "No compatibility information") + ")" + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Column + { + id: compatibleSupportMaterialColumn + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible support materials") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Repeater + { + model: packageData.compatibleSupportMaterials + + Label + { + width: compatibleSupportMaterialColumn.width + + text: modelData + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Label + { + width: parent.width + + visible: packageData.compatibleSupportMaterials.length == 0 + text: "(" + catalog.i18nc("@info No materials", "None") + ")" + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Column + { + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Compatible with Material Station") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width + + text: packageData.isCompatibleMaterialStation ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Column + { + width: parent.width - parent.padding * 2 + + visible: packageData.packageType === "material" + spacing: 0 + + Label + { + width: parent.width + + text: catalog.i18nc("@header", "Optimized for Air Manager") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + + Label + { + width: parent.width + + text: packageData.isCompatibleAirManager ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + + Row + { + id: externalButtonRow + anchors.horizontalCenter: parent.horizontalCenter + + spacing: UM.Theme.getSize("narrow_margin").width + + Cura.SecondaryButton + { + text: packageData.packageType === "plugin" ? catalog.i18nc("@button", "Visit plug-in website") : catalog.i18nc("@button", "Website") + iconSource: UM.Theme.getIcon("Globe") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Buy spool") + iconSource: UM.Theme.getIcon("ShoppingCart") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.whereToBuy) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Safety datasheet") + iconSource: UM.Theme.getIcon("Warning") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.safetyDataSheet) + } + + Cura.SecondaryButton + { + visible: packageData.packageType === "material" + text: catalog.i18nc("@button", "Technical datasheet") + iconSource: UM.Theme.getIcon("DocumentFilled") + outlineColor: "transparent" + onClicked: Qt.openUrlExternally(packageData.technicalDataSheet) + } + } + } + } + + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } +} -- cgit v1.2.3 From db09954ac853f261390f8f1f58e0936281057852 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 10:59:28 +0100 Subject: Fixed returning an error after the package is destroyed Defensive programming Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index e3af31d81a..f7dcd29feb 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -237,11 +237,17 @@ class PackageList(ListModel): if reply: reply_string = bytes(reply.readAll()).decode() Logger.error(f"Failed to download package: {package_id} due to {reply_string}") - package = self.getPackageModel(package_id) - if update: - package.is_updating = ManageState.FAILED - else: - package.is_installing = ManageState.FAILED + try: + package = self.getPackageModel(package_id) + if update: + package.is_updating = ManageState.FAILED + else: + package.is_installing = ManageState.FAILED + 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 + # was deleted when it was still parsing the response + return def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: """Subscribe the user (if logged in) to the package for a given SDK -- cgit v1.2.3 From 27cb1d2d9f858f5c24d2363a5a1ca32baedabf33 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 8 Dec 2021 14:04:19 +0100 Subject: Also have/keep the uninstalled status displayed. 'isRecentlyInstalled' was both for installing _and_ uninstalling. Will add the rename to the refactor later on. part of CURA-8587 --- plugins/Marketplace/PackageModel.py | 5 +++-- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index c17769d6ef..b1b6db733d 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal +from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. @@ -371,8 +372,8 @@ class PackageModel(QObject): self.stateManageButtonChanged.emit() @pyqtProperty(bool, notify = stateManageButtonChanged) - def isRecentlyInstalled(self): - return self._is_recently_installed + def installationStatus(self): + return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() @property def can_downgrade(self) -> bool: diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index b43ea580ba..f552bd8e69 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -361,7 +361,7 @@ Rectangle secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "Uninstalling...") confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") - confirmedTextChoice: packageData.isRecentlyInstalled + confirmedTextChoice: packageData.installationStatus enabled: !(enableManageButton.busy || updateManageButton.busy) onClicked: -- cgit v1.2.3 From fd508342fe2b96669d22913942ba76616609db75 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 14:33:22 +0100 Subject: Renamed Manager to RestartManager Contributes to: CURA-8587 --- plugins/Marketplace/Manager.py | 32 -------------------- plugins/Marketplace/Marketplace.py | 4 +-- plugins/Marketplace/RestartManager.py | 36 +++++++++++++++++++++++ plugins/Marketplace/resources/qml/Marketplace.qml | 4 +-- 4 files changed, 40 insertions(+), 36 deletions(-) delete mode 100644 plugins/Marketplace/Manager.py create mode 100644 plugins/Marketplace/RestartManager.py diff --git a/plugins/Marketplace/Manager.py b/plugins/Marketplace/Manager.py deleted file mode 100644 index f367579079..0000000000 --- a/plugins/Marketplace/Manager.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional - -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal - -from cura.CuraApplication import CuraApplication -from UM.PluginRegistry import PluginRegistry - -class Manager(QObject): - def __init__(self, parent: Optional[QObject] = None): - super().__init__(parent = parent) - self._manager: "CuraPackageManager" = CuraApplication.getInstance().getPackageManager() - self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() - - self._manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) - - self._restart_needed = False - - def checkIfRestartNeeded(self): - if self._manager.hasPackagesToRemoveOrInstall or len(self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: - self._restart_needed = True - else: - self._restart_needed = False - self.showRestartNotificationChanged.emit() - - showRestartNotificationChanged = pyqtSignal() - - @pyqtProperty(bool, notify = showRestartNotificationChanged) - def showRestartNotification(self) -> bool: - return self._restart_needed diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 858ea867ee..5ebc7830ed 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -13,7 +13,7 @@ from UM.PluginRegistry import PluginRegistry # To find out where we are stored from .RemotePackageList import RemotePackageList # To register this type with QML. from .LocalPackageList import LocalPackageList # To register this type with QML. -from .Manager import Manager # To register this type with QML. +from .RestartManager import RestartManager # To register this type with QML. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -31,7 +31,7 @@ class Marketplace(Extension): qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") - qmlRegisterType(Manager, "Marketplace", 1, 0, "Manager") + qmlRegisterType(RestartManager, "Marketplace", 1, 0, "RestartManager") @pyqtSlot() def show(self) -> None: diff --git a/plugins/Marketplace/RestartManager.py b/plugins/Marketplace/RestartManager.py new file mode 100644 index 0000000000..5b66ca0292 --- /dev/null +++ b/plugins/Marketplace/RestartManager.py @@ -0,0 +1,36 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional, TYPE_CHECKING + +from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal + +from cura.CuraApplication import CuraApplication + +if TYPE_CHECKING: + from UM.PluginRegistry import PluginRegistry + from cura.CuraPackageManager import CuraPackageManager + + +class RestartManager(QObject): + def __init__(self, parent: Optional[QObject] = None): + super().__init__(parent = parent) + self._manager: "CuraPackageManager" = CuraApplication.getInstance().getPackageManager() + self._plugin_registry: "PluginRegistry" = CuraApplication.getInstance().getPluginRegistry() + + self._manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) + self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) + + self._restart_needed = False + + def checkIfRestartNeeded(self): + if self._manager.hasPackagesToRemoveOrInstall or len(self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: + self._restart_needed = True + else: + self._restart_needed = False + self.showRestartNotificationChanged.emit() + + showRestartNotificationChanged = pyqtSignal() + + @pyqtProperty(bool, notify = showRestartNotificationChanged) + def showRestartNotification(self) -> bool: + return self._restart_needed diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index c04ef5c027..017a9e3dde 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -14,7 +14,7 @@ Window { id: marketplaceDialog property variant catalog: UM.I18nCatalog { name: "cura" } - property variant manager: Marketplace.Manager { } + property variant restartManager: Marketplace.RestartManager { } signal searchStringChanged(string new_search) @@ -234,7 +234,7 @@ Window { height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("primary") - visible: manager.showRestartNotification + visible: restartManager.showRestartNotification anchors { left: parent.left -- cgit v1.2.3 From 4a436b5598f9df2dfdd0f192539ecc5943b0f737 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 8 Dec 2021 14:46:29 +0100 Subject: Display different types of manage buttons through Loader.sourceComponent cura 8734 --- plugins/Marketplace/resources/qml/ManageButton.qml | 289 +++++++-------------- plugins/Marketplace/resources/qml/PackageCard.qml | 17 +- plugins/Marketplace/resources/qml/PackagePage.qml | 12 +- 3 files changed, 108 insertions(+), 210 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index b2a3ec7f1b..0614e90c87 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -11,226 +11,131 @@ import Cura 1.6 as Cura RowLayout { id: manageButton - property alias primaryText: primaryButton.text - property alias secondaryText: secondaryButton.text - property string busyPrimaryText: busyMessageText.text - property string busySecondaryText: busyMessageText.text - property string confirmedPrimaryText: confirmedMessageText.text - property string confirmedSecondaryText: confirmedMessageText.text - property bool busy - property bool confirmed + property string button_style + property string primaryText + property string secondaryText + property string busyPrimaryText + property string busySecondaryText + property string confirmedPrimaryText + property string confirmedSecondaryText property bool confirmedTextChoice: true signal clicked(bool primary_action) - Cura.PrimaryButton + property Component primaryButton: Component { - id: primaryButton - enabled: manageButton.enabled - - onClicked: + Cura.PrimaryButton { - busyMessage.text = manageButton.busyPrimaryText - confirmedMessage.text = manageButton.confirmedPrimaryText - manageButton.clicked(true) + id: primaryButton + enabled: manageButton.enabled + text: manageButton.primaryText + + onClicked: + { + manageButton.confirmedTextChoice = true + manageButton.clicked(true) + } } } - Cura.SecondaryButton + property Component secondaryButton: Component { - id: secondaryButton - enabled: manageButton.enabled - - onClicked: + Cura.SecondaryButton { - busyMessage.text = manageButton.busySecondaryText - confirmedMessage.text = manageButton.confirmedSecondaryText - manageButton.clicked(false) + id: secondaryButton + enabled: manageButton.enabled + text: manageButton.secondaryText + + onClicked: + { + manageButton.confirmedTextChoice = false + manageButton.clicked(false) + } } } - Item + property Component busyButton: Component { - id: busyMessage - property alias text: busyMessageText.text - height: UM.Theme.getSize("action_button").height - width: childrenRect.width - - UM.RecolorImage + Item { - id: busyIndicator - width: height - anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: UM.Theme.getSize("narrow_margin").height - anchors.bottom: parent.bottom - anchors.bottomMargin: anchors.topMargin - - source: UM.Theme.getIcon("Spinner") - color: UM.Theme.getColor("primary") - - RotationAnimator - { - target: busyIndicator - running: busyMessage.visible - from: 0 - to: 360 - loops: Animation.Infinite - duration: 2500 + id: busyMessage + height: UM.Theme.getSize("action_button").height + width: childrenRect.width + + UM.RecolorImage + { + id: busyIndicator + width: height + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: UM.Theme.getSize("narrow_margin").height + anchors.bottom: parent.bottom + anchors.bottomMargin: anchors.topMargin + + source: UM.Theme.getIcon("Spinner") + color: UM.Theme.getColor("primary") + + RotationAnimator + { + target: busyIndicator + running: busyMessage.visible + from: 0 + to: 360 + loops: Animation.Infinite + duration: 2500 + } + } + Label + { + id: busyMessageText + anchors.left: busyIndicator.right + anchors.leftMargin: UM.Theme.getSize("narrow_margin").width + anchors.verticalCenter: parent.verticalCenter + text: manageButton.busyMessageText + + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("primary") } } - Label - { - id: busyMessageText - anchors.left: busyIndicator.right - anchors.leftMargin: UM.Theme.getSize("narrow_margin").width - anchors.verticalCenter: parent.verticalCenter - - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("primary") - } } - Item + property Component confirmButton: Component { - id: confirmedMessage - property alias text: confirmedMessageText.text + Item + { - height: UM.Theme.getSize("action_button").height - width: childrenRect.width + height: UM.Theme.getSize("action_button").height + width: childrenRect.width - Label - { - id: confirmedMessageText - anchors.verticalCenter: parent.verticalCenter + Label + { + id: confirmedMessageText + anchors.verticalCenter: parent.verticalCenter + text: manageButton.confirmedTextChoice ? manageButton.confirmedPrimaryText : manageButton.confirmedSecondaryText - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("primary") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("primary") + } } } - states: - [ - State - { - name: "primary" - PropertyChanges - { - target: primaryButton - visible: true - } - PropertyChanges - { - target: secondaryButton - visible: false - } - PropertyChanges - { - target: busyMessage - visible: false - } - PropertyChanges - { - target: confirmedMessage - visible: false - } - }, - State - { - name: "secondary" - PropertyChanges - { - target: primaryButton - visible: false - } - PropertyChanges - { - target: secondaryButton - visible: true - } - PropertyChanges - { - target: busyMessage - visible: false - } - PropertyChanges - { - target: confirmedMessage - visible: false - } - }, - State - { - name: "hidden" - PropertyChanges - { - target: primaryButton - visible: false - } - PropertyChanges - { - target: secondaryButton - visible: false - } - PropertyChanges - { - target: busyMessage - visible: false - } - PropertyChanges - { - target: confirmedMessage - visible: false - } - }, - State - { - name: "busy" - PropertyChanges - { - target: primaryButton - visible: false - } - PropertyChanges - { - target: secondaryButton - visible: false - } - PropertyChanges - { - target: busyMessage - visible: true - } - PropertyChanges - { - target: confirmedMessage - visible: false - } - }, - State + Loader + { + sourceComponent: { - name: "confirmed" - PropertyChanges - { - target: primaryButton - visible: false - } - PropertyChanges - { - target: secondaryButton - visible: false - } - PropertyChanges - { - target: busyMessage - visible: false - } - PropertyChanges - { - target: confirmedMessage - visible: true - text: manageButton.confirmedTextChoice ? manageButton.confirmedPrimaryText : manageButton.confirmedSecondaryText + 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; } } - ] + } } diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 1625f6cff3..6893b70e56 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -29,6 +29,7 @@ Rectangle width: parent.width height: UM.Theme.getSize("card").height + // card icon Image { id: packageItem @@ -44,6 +45,7 @@ Rectangle source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" } + // ColumnLayout { anchors @@ -142,6 +144,7 @@ Rectangle } } + // description Item { id: shortDescription @@ -222,6 +225,7 @@ Rectangle spacing: UM.Theme.getSize("narrow_margin").width + // label "By" Label { id: authorBy @@ -232,6 +236,7 @@ Rectangle color: UM.Theme.getColor("text") } + // clickable author name Cura.TertiaryButton { Layout.fillWidth: true @@ -252,9 +257,7 @@ Rectangle ManageButton { id: enableManageButton - state: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" - busy: packageData.enableManageButton == "busy" - confirmed: packageData.enableManageButton == "confirmed" + button_style: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") busyPrimaryText: catalog.i18nc("@button", "Enabling...") @@ -280,9 +283,7 @@ Rectangle ManageButton { id: installManageButton - state: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" - busy: packageData.stateManageInstallButton == "busy" - confirmed: packageData.stateManageInstallButton == "confirmed" + button_style: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") busyPrimaryText: catalog.i18nc("@button", "Installing...") @@ -309,9 +310,7 @@ Rectangle ManageButton { id: updateManageButton - state: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" - busy: packageData.stateManageUpdateButton == "busy" - confirmed: packageData.stateManageUpdateButton == "confirmed" + button_style: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "Updating...") diff --git a/plugins/Marketplace/resources/qml/PackagePage.qml b/plugins/Marketplace/resources/qml/PackagePage.qml index 1669f8ffc7..5529373ba3 100644 --- a/plugins/Marketplace/resources/qml/PackagePage.qml +++ b/plugins/Marketplace/resources/qml/PackagePage.qml @@ -207,9 +207,7 @@ Rectangle ManageButton { id: enableManageButton - state: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" - busy: packageData.enableManageButton == "busy" - confirmed: packageData.enableManageButton == "confirmed" + button_style: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Enable") busyPrimaryText: catalog.i18nc("@button", "Enabling...") @@ -235,9 +233,7 @@ Rectangle ManageButton { id: installManageButton - state: !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" - busy: packageData.stateManageInstallButton == "busy" - confirmed: packageData.stateManageInstallButton == "confirmed" + button_style: !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Install") busyPrimaryText: catalog.i18nc("@button", "Installing...") @@ -264,9 +260,7 @@ Rectangle ManageButton { id: updateManageButton - state: !installManageButton.confirmed || updateManageButton.confirmed ? packageData.stateManageUpdateButton : "hidden" - busy: packageData.stateManageUpdateButton == "busy" - confirmed: packageData.stateManageUpdateButton == "confirmed" + button_style: !installManageButton.confirmed || updateManageButton.confirmed ? packageData.stateManageUpdateButton : "hidden" Layout.alignment: Qt.AlignTop primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "Updating...") -- cgit v1.2.3 From e0ca0d5446ace8495487bc98fd46af05b229c98d Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 14:46:34 +0100 Subject: Renamed recently_install to better depict the usage This flag is an indication if the package. was recently un-/installed Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 2 +- plugins/Marketplace/PackageModel.py | 18 +++++++++--------- plugins/Marketplace/RemotePackageList.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 6fb9cfbf34..b9f3253b85 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -68,7 +68,7 @@ class LocalPackageList(PackageList): 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.is_recently_installed = True + package.installation_status_changed = True return package def checkForUpdates(self, packages: List[Dict[str, Any]]): diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index b1b6db733d..fbc998b5c9 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -70,7 +70,7 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing: ManageState = ManageState.HALTED - self._is_recently_installed = False + self._installation_status_changed = False self._is_recently_updated = False self._is_recently_enabled = False @@ -338,7 +338,7 @@ class PackageModel(QObject): """The state of the Manage Install package card""" if self._is_installing == ManageState.PROCESSING: return "busy" - if self._is_recently_installed: + if self._installation_status_changed: return "confirmed" if self._is_installed: if self._is_bundled and not self._can_downgrade: @@ -358,17 +358,17 @@ class PackageModel(QObject): if value != self._is_installing: self._is_installing = value if value == ManageState.HALTED: - self._is_recently_installed = True + self._installation_status_changed = True self.stateManageButtonChanged.emit() @property - def is_recently_installed(self): - return self._is_recently_installed + def installation_status_changed(self): + return self._installation_status_changed - @is_recently_installed.setter - def is_recently_installed(self, value): - if value != self._is_recently_installed: - self._is_recently_installed = value + @installation_status_changed.setter + def installation_status_changed(self, value): + if value != self._installation_status_changed: + self._installation_status_changed = value self.stateManageButtonChanged.emit() @pyqtProperty(bool, notify = stateManageButtonChanged) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index d20cb5f4b0..7228507bf5 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -124,7 +124,7 @@ class RemotePackageList(PackageList): package = PackageModel(package_data, parent = self) self._connectManageButtonSignals(package) if package_id in self._manager.getPackagesToRemove() or package_id in self._manager.getPackagesToInstall(): - package.is_recently_installed = True + 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 -- cgit v1.2.3 From df0c502961320bb4ed38935824473b511fe2e803 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 14:48:47 +0100 Subject: Removed redundant pyQtSlots Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index f7dcd29feb..21c1da004b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -279,7 +279,6 @@ class PackageList(ListModel): package.enablePackageTriggered.connect(self.enablePackage) package.disablePackageTriggered.connect(self.disablePackage) - @pyqtSlot(str) def installPackage(self, package_id: str) -> None: """Install a package from the Marketplace @@ -290,7 +289,6 @@ class PackageList(ListModel): url = package.download_url self.download(package_id, url, False) - @pyqtSlot(str) def uninstallPackage(self, package_id: str) -> None: """Uninstall a package from the Marketplace @@ -302,7 +300,6 @@ class PackageList(ListModel): self.unsunscribeUserFromPackage(package_id) package.is_installing = ManageState.HALTED - @pyqtSlot(str) def updatePackage(self, package_id: str) -> None: """Update a package from the Marketplace @@ -314,7 +311,6 @@ class PackageList(ListModel): url = package.download_url self.download(package_id, url, True) - @pyqtSlot(str) def enablePackage(self, package_id: str) -> None: """Enable a package in the plugin registry @@ -326,7 +322,6 @@ class PackageList(ListModel): package.is_active = True package.is_enabling = ManageState.HALTED - @pyqtSlot(str) def disablePackage(self, package_id: str) -> None: """Disable a package in the plugin registry -- cgit v1.2.3 From c72fd12ea2b44b1380b3af372e9c96a0bfb4ea88 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 8 Dec 2021 15:50:00 +0100 Subject: Make ManageButton a reusable Component cura 8734 --- plugins/Marketplace/resources/qml/ManageButton.qml | 30 +++---- plugins/Marketplace/resources/qml/PackageCard.qml | 95 +++++++++++++++++----- plugins/Marketplace/resources/qml/PackagePage.qml | 95 +++++++++++++++++----- 3 files changed, 158 insertions(+), 62 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 0614e90c87..7843805e26 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -8,17 +8,13 @@ import QtQuick.Layouts 1.1 import UM 1.6 as UM import Cura 1.6 as Cura -RowLayout +Item { id: manageButton property string button_style - property string primaryText - property string secondaryText - property string busyPrimaryText - property string busySecondaryText - property string confirmedPrimaryText - property string confirmedSecondaryText - property bool confirmedTextChoice: true + property string text + property bool busy + property bool confirmed signal clicked(bool primary_action) @@ -27,12 +23,10 @@ RowLayout Cura.PrimaryButton { id: primaryButton - enabled: manageButton.enabled - text: manageButton.primaryText + text: manageButton.text onClicked: { - manageButton.confirmedTextChoice = true manageButton.clicked(true) } } @@ -43,12 +37,10 @@ RowLayout Cura.SecondaryButton { id: secondaryButton - enabled: manageButton.enabled - text: manageButton.secondaryText + text: manageButton.text onClicked: { - manageButton.confirmedTextChoice = false manageButton.clicked(false) } } @@ -59,8 +51,6 @@ RowLayout Item { id: busyMessage - height: UM.Theme.getSize("action_button").height - width: childrenRect.width UM.RecolorImage { @@ -91,7 +81,7 @@ RowLayout anchors.left: busyIndicator.right anchors.leftMargin: UM.Theme.getSize("narrow_margin").width anchors.verticalCenter: parent.verticalCenter - text: manageButton.busyMessageText + text: manageButton.text font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("primary") @@ -111,7 +101,7 @@ RowLayout { id: confirmedMessageText anchors.verticalCenter: parent.verticalCenter - text: manageButton.confirmedTextChoice ? manageButton.confirmedPrimaryText : manageButton.confirmedSecondaryText + text: manageButton.text font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("primary") @@ -119,8 +109,12 @@ RowLayout } } + height: UM.Theme.getSize("action_button").height + width: childrenRect.width + Loader { + sourceComponent: { switch (manageButton.button_style) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 6893b70e56..5c986c2e6e 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -257,15 +257,34 @@ Rectangle ManageButton { id: enableManageButton - button_style: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" + visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + button_style: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Enable") - busyPrimaryText: catalog.i18nc("@button", "Enabling...") - confirmedPrimaryText: catalog.i18nc("@button", "Enabled") - secondaryText: catalog.i18nc("@button", "Disable") - busySecondaryText: catalog.i18nc("@button", "Disabling...") - confirmedSecondaryText: catalog.i18nc("@button", "Disabled") - enabled: !(installManageButton.busy || updateManageButton.busy) + 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 onClicked: { @@ -283,16 +302,34 @@ Rectangle ManageButton { id: installManageButton - button_style: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" + visible: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) + button_style: packageData.stateManageInstallButton + busy: packageData.stateManageInstallButton == "busy" + confirmed: packageData.stateManageInstallButton == "confirmed" Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Install") - busyPrimaryText: catalog.i18nc("@button", "Installing...") - confirmedPrimaryText: catalog.i18nc("@button", "Installed") - secondaryText: catalog.i18nc("@button", "Uninstall") - busySecondaryText: catalog.i18nc("@button", "Uninstalling...") - confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") - confirmedTextChoice: packageData.installationStatus - enabled: !(enableManageButton.busy || updateManageButton.busy) + 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 ""; + } + } + enabled: !enableManageButton.busy && !updateManageButton.busy onClicked: { @@ -310,12 +347,26 @@ Rectangle ManageButton { id: updateManageButton - button_style: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageUpdateButton : "hidden" + visible: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) + + button_style: packageData.stateManageUpdateButton + busy: packageData.stateManageUpdateButton == "busy" + confirmed: packageData.stateManageUpdateButton == "confirmed" Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Update") - busyPrimaryText: catalog.i18nc("@button", "Updating...") - confirmedPrimaryText: catalog.i18nc("@button", "Updated") - enabled: !(installManageButton.busy || enableManageButton.busy) + 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 ""; + } + } onClicked: packageData.updatePackageTriggered(packageData.packageId) } diff --git a/plugins/Marketplace/resources/qml/PackagePage.qml b/plugins/Marketplace/resources/qml/PackagePage.qml index 5529373ba3..fca25c4022 100644 --- a/plugins/Marketplace/resources/qml/PackagePage.qml +++ b/plugins/Marketplace/resources/qml/PackagePage.qml @@ -207,15 +207,34 @@ Rectangle ManageButton { id: enableManageButton - button_style: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed ? packageData.stateManageEnableButton : "hidden" + visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + button_style: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Enable") - busyPrimaryText: catalog.i18nc("@button", "Enabling...") - confirmedPrimaryText: catalog.i18nc("@button", "Enabled") - secondaryText: catalog.i18nc("@button", "Disable") - busySecondaryText: catalog.i18nc("@button", "Disabling...") - confirmedSecondaryText: catalog.i18nc("@button", "Disabled") - enabled: !(installManageButton.busy || updateManageButton.busy) + 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 onClicked: { @@ -233,16 +252,34 @@ Rectangle ManageButton { id: installManageButton - button_style: !(enableManageButton.confirmed || updateManageButton.confirmed) ? packageData.stateManageInstallButton : "hidden" + visible: !(enableManageButton.confirmed || updateManageButton.confirmed) + button_style: packageData.stateManageInstallButton + busy: packageData.stateManageInstallButton == "busy" + confirmed: packageData.stateManageInstallButton == "confirmed" Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Install") - busyPrimaryText: catalog.i18nc("@button", "Installing...") - confirmedPrimaryText: catalog.i18nc("@button", "Installed") - secondaryText: catalog.i18nc("@button", "Uninstall") - busySecondaryText: catalog.i18nc("@button", "Uninstalling...") - confirmedSecondaryText: catalog.i18nc("@button", "Uninstalled") - confirmedTextChoice: packageData.isRecentlyInstalled - enabled: !(enableManageButton.busy || updateManageButton.busy) + 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 ""; + } + } + enabled: !enableManageButton.busy && !updateManageButton.busy onClicked: { @@ -260,12 +297,26 @@ Rectangle ManageButton { id: updateManageButton - button_style: !installManageButton.confirmed || updateManageButton.confirmed ? packageData.stateManageUpdateButton : "hidden" + visible: !installManageButton.confirmed || updateManageButton.confirmed + + button_style: packageData.stateManageUpdateButton + busy: packageData.stateManageUpdateButton == "busy" + confirmed: packageData.stateManageUpdateButton == "confirmed" Layout.alignment: Qt.AlignTop - primaryText: catalog.i18nc("@button", "Update") - busyPrimaryText: catalog.i18nc("@button", "Updating...") - confirmedPrimaryText: catalog.i18nc("@button", "Updated") - enabled: !(installManageButton.busy || enableManageButton.busy) + 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 ""; + } + } onClicked: packageData.updatePackageTriggered(packageData.packageId) } -- cgit v1.2.3 From e4d469b6a1404d7e575995605ae171aa56fd307d Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 16:13:32 +0100 Subject: Added some typing annotation Contributes to: CURA-8587 --- plugins/Marketplace/RestartManager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/RestartManager.py b/plugins/Marketplace/RestartManager.py index 5b66ca0292..3fa6ada797 100644 --- a/plugins/Marketplace/RestartManager.py +++ b/plugins/Marketplace/RestartManager.py @@ -2,17 +2,18 @@ # Cura is released under the terms of the LGPLv3 or higher. from typing import Optional, TYPE_CHECKING -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal +from PyQt5.QtCore import pyqtProperty, pyqtSignal from cura.CuraApplication import CuraApplication if TYPE_CHECKING: + from PyQt5.QtCore import QObject from UM.PluginRegistry import PluginRegistry from cura.CuraPackageManager import CuraPackageManager class RestartManager(QObject): - def __init__(self, parent: Optional[QObject] = None): + def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent = parent) self._manager: "CuraPackageManager" = CuraApplication.getInstance().getPackageManager() self._plugin_registry: "PluginRegistry" = CuraApplication.getInstance().getPluginRegistry() @@ -22,7 +23,7 @@ class RestartManager(QObject): self._restart_needed = False - def checkIfRestartNeeded(self): + def checkIfRestartNeeded(self) -> None: if self._manager.hasPackagesToRemoveOrInstall or len(self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: self._restart_needed = True else: -- cgit v1.2.3 From 4c516e8cec2e2f24f4da10491fee2e819ef3e3cc Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 16:14:59 +0100 Subject: Moved QObject out if TYPE_CHECKING statement Contributes to: CURA-8587 --- plugins/Marketplace/RestartManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Marketplace/RestartManager.py b/plugins/Marketplace/RestartManager.py index 3fa6ada797..19650dd64e 100644 --- a/plugins/Marketplace/RestartManager.py +++ b/plugins/Marketplace/RestartManager.py @@ -2,12 +2,11 @@ # Cura is released under the terms of the LGPLv3 or higher. from typing import Optional, TYPE_CHECKING -from PyQt5.QtCore import pyqtProperty, pyqtSignal +from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from cura.CuraApplication import CuraApplication if TYPE_CHECKING: - from PyQt5.QtCore import QObject from UM.PluginRegistry import PluginRegistry from cura.CuraPackageManager import CuraPackageManager -- cgit v1.2.3 From 9f41115bc144f31636b90a2ea5d5d1ce9061baa2 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 8 Dec 2021 18:47:56 +0100 Subject: Rework of the ManageButton Now uses the internal signal Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 4 +- plugins/Marketplace/PackageList.py | 25 +--- plugins/Marketplace/PackageModel.py | 166 ++++++++++----------- plugins/Marketplace/RemotePackageList.py | 2 - plugins/Marketplace/resources/qml/ManageButton.qml | 19 +-- plugins/Marketplace/resources/qml/PackageCard.qml | 108 +++++--------- plugins/Marketplace/resources/qml/PackagePage.qml | 108 +++++--------- 7 files changed, 169 insertions(+), 263 deletions(-) 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" + def setIsUpdating(self, value): + if value != self._is_updating: + self._is_updating = value + self.stateManageButtonChanged.emit() - @property - def is_updating(self) -> ManageState: - """Flag indicating if the package is being updated""" + @pyqtProperty(bool, fset = setIsUpdating, notify = stateManageButtonChanged) + def isUpdating(self): return self._is_updating - @is_updating.setter - def is_updating(self, value: ManageState) -> None: - if value != self._is_updating: - self._is_updating = value - if value == ManageState.HALTED: - self._is_recently_updated = True + 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/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 5c986c2e6e..897f5abca7 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -257,34 +257,16 @@ Rectangle ManageButton { id: enableManageButton - visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + visible: root.manageableInListView && !(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: { @@ -302,34 +284,31 @@ Rectangle ManageButton { id: installManageButton - visible: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) + visible: (root.manageableInListView || 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: { @@ -347,25 +326,20 @@ Rectangle ManageButton { id: updateManageButton - visible: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) + visible: (root.manageableInListView && 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) diff --git a/plugins/Marketplace/resources/qml/PackagePage.qml b/plugins/Marketplace/resources/qml/PackagePage.qml index fca25c4022..12d24fb812 100644 --- a/plugins/Marketplace/resources/qml/PackagePage.qml +++ b/plugins/Marketplace/resources/qml/PackagePage.qml @@ -207,34 +207,16 @@ Rectangle ManageButton { id: enableManageButton - visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + visible: !(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: { @@ -252,34 +234,31 @@ Rectangle ManageButton { id: installManageButton - visible: !(enableManageButton.confirmed || updateManageButton.confirmed) + visible: 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: { @@ -297,25 +276,20 @@ Rectangle ManageButton { id: updateManageButton - visible: !installManageButton.confirmed || updateManageButton.confirmed + visible: 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) -- cgit v1.2.3 From d405652db7f511f59657c131e17995c988663db2 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 8 Dec 2021 19:41:50 +0100 Subject: Combine dublicated elements from `PackageCard` and `PackagePage` into reusable component `PackageCardHeader` cura 8734 --- plugins/Marketplace/resources/qml/PackageCard.qml | 394 ++++----------------- .../resources/qml/PackageCardHeader.qml | 299 ++++++++++++++++ plugins/Marketplace/resources/qml/PackagePage.qml | 271 +------------- 3 files changed, 364 insertions(+), 600 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackageCardHeader.qml diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 5c986c2e6e..9097417c80 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -10,9 +10,8 @@ import Cura 1.6 as Cura Rectangle { - id: root - property var packageData - property bool manageableInListView + property alias packageData: packageCardHeader.packageData + property alias manageableInListView: packageCardHeader.showManageButtons height: childrenRect.height color: UM.Theme.getColor("main_background") @@ -24,352 +23,79 @@ Rectangle spacing: 0 - Item + PackageCardHeader { - width: parent.width - height: UM.Theme.getSize("card").height - - // card icon - Image - { - id: packageItem - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - width: UM.Theme.getSize("card_icon").width - height: width - - source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" - } - - // - ColumnLayout - { - anchors - { - left: packageItem.right - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - top: parent.top - topMargin: UM.Theme.getSize("narrow_margin").height - } - height: packageItem.height + packageItem.anchors.margins * 2 - - // Title row. - RowLayout - { - id: titleBar - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - Label - { - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignTop - } - VerifiedIcon - { - enabled: packageData.isCheckedByUltimaker - visible: packageData.isCheckedByUltimaker - } - - - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered - } - - UM.RecolorImage - { - anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO - } - - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } - - Label - { - id: packageVersionLabel - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - Layout.fillWidth: true - } - - Button - { - id: externalLinkButton - - // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: UM.Theme.getSize("narrow_margin").width - topPadding: UM.Theme.getSize("narrow_margin").width - bottomPadding: UM.Theme.getSize("narrow_margin").width - - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - contentItem: UM.RecolorImage - { - source: UM.Theme.getIcon("LinkExternal") - color: UM.Theme.getColor("icon") - implicitWidth: UM.Theme.getSize("card_tiny_icon").width - implicitHeight: UM.Theme.getSize("card_tiny_icon").height - } - - background: Rectangle - { - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" - radius: externalLinkButton.width / 2 - } - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - } + id: packageCardHeader // description - Item - { - id: shortDescription - Layout.preferredWidth: parent.width - Layout.fillHeight: true - - Label - { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - - text: packageData.description - textFormat: Text.PlainText //Must be plain text, or we won't get onLineLaidOut signals. Don't auto-detect! - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight - visible: text !== "" - - onLineLaidOut: - { - if(truncated && line.isLast) - { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else - { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. - } - descriptionLabel.lastLineWidth = line.implicitWidth; - } - } - } - Label - { - id: tripleDotLabel - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: descriptionLabel.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated && descriptionLabel.text !== "" - } - Cura.TertiaryButton - { - id: readMoreButton - anchors.right: parent.right - anchors.bottom: descriptionLabel.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated && descriptionLabel.text !== "" - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true + Item + { + id: shortDescription - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } - } + anchors.fill: parent - // Author and action buttons. - RowLayout + Label { - id: authorAndActionButton - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - spacing: UM.Theme.getSize("narrow_margin").width - - // label "By" - Label - { - id: authorBy - Layout.alignment: Qt.AlignCenter - - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } - - // clickable author name - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignCenter - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - - ManageButton - { - id: enableManageButton - visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed - 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 - - onClicked: - { - if (primary_action) - { - packageData.enablePackageTriggered(packageData.packageId) - } - else - { - packageData.disablePackageTriggered(packageData.packageId) - } - } - } - - ManageButton + id: descriptionLabel + width: parent.width + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + + text: packageData.description + textFormat: Text.PlainText //Must be plain text, or we won't get onLineLaidOut signals. Don't auto-detect! + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + visible: text !== "" + + onLineLaidOut: { - id: installManageButton - visible: (root.manageableInListView || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) - 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 ""; - } - } - enabled: !enableManageButton.busy && !updateManageButton.busy - - onClicked: + if(truncated && line.isLast) { - if (primary_action) + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) { - packageData.installPackageTriggered(packageData.packageId) + line.width = max_line_width; } else { - packageData.uninstallPackageTriggered(packageData.packageId) + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. } + descriptionLabel.lastLineWidth = line.implicitWidth; } } - - ManageButton - { - id: updateManageButton - visible: (root.manageableInListView) && (!installManageButton.confirmed || updateManageButton.confirmed) - - button_style: packageData.stateManageUpdateButton - busy: packageData.stateManageUpdateButton == "busy" - confirmed: packageData.stateManageUpdateButton == "confirmed" - 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 ""; - } - } - - onClicked: packageData.updatePackageTriggered(packageData.packageId) - } + } + Label + { + id: tripleDotLabel + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: descriptionLabel.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated && descriptionLabel.text !== "" + } + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: descriptionLabel.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated && descriptionLabel.text !== "" + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } } } diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml new file mode 100644 index 0000000000..4719fcfe13 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -0,0 +1,299 @@ +// 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 + +// As both the PackageCard and Package contain similar components; a package icon, title, author bar. These components +// are combined into the reusable "PackageCardHeader" component +Item +{ + default property alias contents: contentItem.children; + + property var packageData + property bool showManageButtons + + width: parent.width + height: UM.Theme.getSize("card").height + + // card icon + Image + { + id: packageItem + anchors + { + top: parent.top + left: parent.left + margins: UM.Theme.getSize("default_margin").width + } + width: UM.Theme.getSize("card_icon").width + height: width + + source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" + } + + ColumnLayout + { + anchors + { + left: packageItem.right + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + top: parent.top + topMargin: UM.Theme.getSize("narrow_margin").height + } + height: packageItem.height + packageItem.anchors.margins * 2 + + // Title row. + RowLayout + { + id: titleBar + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + + Label + { + text: packageData.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignTop + } + VerifiedIcon + { + enabled: packageData.isCheckedByUltimaker + visible: packageData.isCheckedByUltimaker + } + + Control + { + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height + Layout.alignment: Qt.AlignCenter + enabled: false // remove! + visible: false // replace packageInfo.XXXXXX + // TODO: waiting for materials card implementation + + Cura.ToolTip + { + tooltipText: "" // TODO + visible: parent.hovered + } + + UM.RecolorImage + { + anchors.fill: parent + + color: UM.Theme.getColor("primary") + source: UM.Theme.getIcon("CheckCircle") // TODO + } + + // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO + } + + Label + { + id: packageVersionLabel + text: packageData.packageVersion + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.fillWidth: true + } + + Button + { + id: externalLinkButton + + // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? + leftPadding: UM.Theme.getSize("narrow_margin").width + rightPadding: UM.Theme.getSize("narrow_margin").width + topPadding: UM.Theme.getSize("narrow_margin").width + bottomPadding: UM.Theme.getSize("narrow_margin").width + + Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding + contentItem: UM.RecolorImage + { + source: UM.Theme.getIcon("LinkExternal") + color: UM.Theme.getColor("icon") + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + } + + background: Rectangle + { + color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" + radius: externalLinkButton.width / 2 + } + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } + } + + // When a package Card companent is created and children are provided to it they are rendered here + Item { + id: contentItem + Layout.fillHeight: true + Layout.preferredWidth: parent.width + } + + // Author and action buttons. + RowLayout + { + id: authorAndActionButton + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + + spacing: UM.Theme.getSize("narrow_margin").width + + // label "By" + Label + { + id: authorBy + Layout.alignment: Qt.AlignCenter + + text: catalog.i18nc("@label", "By") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } + + // clickable author name + Cura.TertiaryButton + { + Layout.fillWidth: true + Layout.preferredHeight: authorBy.height + Layout.alignment: Qt.AlignCenter + + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } + + ManageButton + { + id: enableManageButton + visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + 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 + + onClicked: + { + if (primary_action) + { + packageData.enablePackageTriggered(packageData.packageId) + } + else + { + packageData.disablePackageTriggered(packageData.packageId) + } + } + } + + ManageButton + { + id: installManageButton + visible: (showManageButtons || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) + 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 ""; + } + } + enabled: !enableManageButton.busy && !updateManageButton.busy + + onClicked: + { + if (primary_action) + { + packageData.installPackageTriggered(packageData.packageId) + } + else + { + packageData.uninstallPackageTriggered(packageData.packageId) + } + } + } + + ManageButton + { + id: updateManageButton + visible: showManageButtons && (!installManageButton.confirmed || updateManageButton.confirmed) + + button_style: packageData.stateManageUpdateButton + busy: packageData.stateManageUpdateButton == "busy" + confirmed: packageData.stateManageUpdateButton == "confirmed" + 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 ""; + } + } + + onClicked: packageData.updatePackageTriggered(packageData.packageId) + } + } + } +} diff --git a/plugins/Marketplace/resources/qml/PackagePage.qml b/plugins/Marketplace/resources/qml/PackagePage.qml index fca25c4022..21c400fff2 100644 --- a/plugins/Marketplace/resources/qml/PackagePage.qml +++ b/plugins/Marketplace/resources/qml/PackagePage.qml @@ -11,8 +11,7 @@ import Cura 1.6 as Cura Rectangle { id: root - property var packageData - property bool manageableInListView + property alias packageData: packageCardHeader.packageData height: childrenRect.height color: UM.Theme.getColor("main_background") @@ -29,118 +28,12 @@ Rectangle width: parent.width height: UM.Theme.getSize("card").height - Image + PackageCardHeader { - id: packageItem - anchors - { - top: parent.top - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - width: UM.Theme.getSize("card_icon").width - height: width - - source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg" - } - - ColumnLayout - { - anchors - { - left: packageItem.right - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - top: parent.top - topMargin: UM.Theme.getSize("narrow_margin").height - } - height: packageItem.height + packageItem.anchors.margins * 2 - - // Title row. - RowLayout - { - id: titleBar - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - Label - { - text: packageData.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignTop - } - VerifiedIcon - { - enabled: packageData.isCheckedByUltimaker - visible: packageData.isCheckedByUltimaker - } - - - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered - } - - UM.RecolorImage - { - anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO - } - - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } + id: packageCardHeader + showManageButtons: true - Label - { - id: packageVersionLabel - text: packageData.packageVersion - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - Layout.fillWidth: true - } - - Button - { - id: externalLinkButton - - // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? - leftPadding: UM.Theme.getSize("narrow_margin").width - rightPadding: UM.Theme.getSize("narrow_margin").width - topPadding: UM.Theme.getSize("narrow_margin").width - bottomPadding: UM.Theme.getSize("narrow_margin").width - - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding - contentItem: UM.RecolorImage - { - source: UM.Theme.getIcon("LinkExternal") - color: UM.Theme.getColor("icon") - implicitWidth: UM.Theme.getSize("card_tiny_icon").width - implicitHeight: UM.Theme.getSize("card_tiny_icon").height - } - - background: Rectangle - { - color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent" - radius: externalLinkButton.width / 2 - } - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - } + anchors.fill: parent Row { @@ -167,160 +60,6 @@ Rectangle text: packageData.downloadCount } } - - // Author and action buttons. - RowLayout - { - id: authorAndActionButton - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - spacing: UM.Theme.getSize("narrow_margin").width - - Label - { - id: authorBy - Layout.alignment: Qt.AlignCenter - - text: catalog.i18nc("@label", "By") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } - - Cura.TertiaryButton - { - Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignCenter - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) - } - - ManageButton - { - id: enableManageButton - visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed - 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 - - onClicked: - { - if (primary_action) - { - packageData.enablePackageTriggered(packageData.packageId) - } - else - { - packageData.disablePackageTriggered(packageData.packageId) - } - } - } - - ManageButton - { - id: installManageButton - visible: !(enableManageButton.confirmed || updateManageButton.confirmed) - 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 ""; - } - } - enabled: !enableManageButton.busy && !updateManageButton.busy - - onClicked: - { - if (primary_action) - { - packageData.installPackageTriggered(packageData.packageId) - } - else - { - packageData.uninstallPackageTriggered(packageData.packageId) - } - } - } - - ManageButton - { - id: updateManageButton - visible: !installManageButton.confirmed || updateManageButton.confirmed - - button_style: packageData.stateManageUpdateButton - busy: packageData.stateManageUpdateButton == "busy" - confirmed: packageData.stateManageUpdateButton == "confirmed" - 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 ""; - } - } - - onClicked: packageData.updatePackageTriggered(packageData.packageId) - } - } } } -- cgit v1.2.3 From 7734bf5169fc70852c4035bc482ec54ee51bb8f5 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 07:58:14 +0100 Subject: Show Install and Update buttons in the correct scenario's Contributes to: CURA-8587 --- plugins/Marketplace/PackageModel.py | 21 ++++++++------------- .../Marketplace/resources/qml/PackageCardHeader.qml | 12 ++++++------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 93d41187e1..9996a8e1df 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -64,8 +64,6 @@ class PackageModel(QObject): self._is_installing = False self._install_status_changing = False - self._is_recently_installed = False - self._is_recently_updated = False self._can_update = False self._is_updating = False @@ -93,10 +91,8 @@ class PackageModel(QObject): 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) @@ -362,7 +358,11 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() + return self._package_id in CuraApplication.getInstance().getPackageManager().local_packages + + @pyqtProperty(bool, notify = stateManageButtonChanged) + def isRecentlyInstalled(self) -> bool: + return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() or self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() @pyqtProperty(bool, notify = stateManageButtonChanged) def isUninstalled(self) -> bool: @@ -389,14 +389,9 @@ class PackageModel(QObject): 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 + @pyqtProperty(bool, notify = stateManageButtonChanged) + def isRecentlyUpdated(self): + return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() and self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() @property def can_update(self) -> bool: diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index ca74d9f77d..5859fbcd8e 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && !(installManageButton.confirmed || updateManageButton.confirmed) + visible: showManageButtons && packageData.isInstalled && !(installManageButton.confirmed || updateManageButton.confirmed) enabled: !(installManageButton.busy || updateManageButton.busy) busy: false @@ -208,12 +208,12 @@ Item ManageButton { id: installManageButton - visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled || !updateManageButton.confirmed) + visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled) && !updateManageButton.confirmed enabled: !packageData.isUpdating busy: packageData.isInstalling - confirmed: packageData.isInstalled || packageData.isUninstalled + confirmed: packageData.isRecentlyInstalled button_style: packageData.stateManageInstallButton Layout.alignment: Qt.AlignTop @@ -223,7 +223,7 @@ Item if (packageData.stateManageInstallButton) { if (packageData.isInstalling) { return catalog.i18nc("@button", "Installing..."); } - else if (packageData.isInstalled) { return catalog.i18nc("@button", "Installed"); } + else if (packageData.isRecentlyInstalled) { return catalog.i18nc("@button", "Installed"); } else { return catalog.i18nc("@button", "Install"); } } else @@ -254,7 +254,7 @@ Item enabled: !installManageButton.busy busy: packageData.isUpdating - confirmed: packageData.isUpdated + confirmed: packageData.isRecentlyUpdated button_style: true Layout.alignment: Qt.AlignTop @@ -262,7 +262,7 @@ Item text: { if (packageData.isUpdating) { return catalog.i18nc("@button", "Updating..."); } - else if (packageData.isUpdated) { return catalog.i18nc("@button", "Updated"); } + else if (packageData.isRecentlyUpdated) { return catalog.i18nc("@button", "Updated"); } else { return catalog.i18nc("@button", "Update"); } } -- cgit v1.2.3 From 59470814e2be076e10afa717190a4e39fe267a6e Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 08:30:22 +0100 Subject: Show spinner again Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 2e2ef294d1..fdeb37a87a 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -55,12 +55,11 @@ Item UM.RecolorImage { id: busyIndicator + visible: parent.visible + height: UM.Theme.getSize("action_button").height - 2 * UM.Theme.getSize("narrow_margin").height width: height anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: UM.Theme.getSize("narrow_margin").height - anchors.bottom: parent.bottom - anchors.bottomMargin: anchors.topMargin + anchors.verticalCenter: parent.verticalCenter source: UM.Theme.getIcon("Spinner") color: UM.Theme.getColor("primary") @@ -78,6 +77,7 @@ Item Label { id: busyMessageText + visible: parent.visible anchors.left: busyIndicator.right anchors.leftMargin: UM.Theme.getSize("narrow_margin").width anchors.verticalCenter: parent.verticalCenter -- cgit v1.2.3 From 51a77f683dc0b227089825af9c3235d557a56ba4 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 09:56:41 +0100 Subject: Moved stateManageButton logic out of the packageModel Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 13 ++++- plugins/Marketplace/PackageList.py | 24 +-------- plugins/Marketplace/PackageModel.py | 59 +++++++--------------- .../resources/qml/PackageCardHeader.qml | 12 ++--- 4 files changed, 37 insertions(+), 71 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index bca6494f37..386d249925 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, cast, Dict, List, Tuple, TYPE_CHECKING, Optional +from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional from cura.CuraApplication import CuraApplication # To find some resource types. from cura.Settings.GlobalStack import GlobalStack @@ -20,10 +20,12 @@ class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) self._local_packages: Optional[List[Dict[str, Any]]] = None + self._local_packages_id: Optional[Set[str]] = None self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: self._local_packages = self.getAllLocalPackages() + self._local_packages_id = set(pkg["package_id"] for pkg in self._local_packages) @property def local_packages(self) -> List[Dict[str, Any]]: @@ -34,6 +36,15 @@ class CuraPackageManager(PackageManager): # It's guaranteed to be a list now. return cast(List[Dict[str, Any]], self._local_packages) + @property + def local_packages_id(self) -> Set[str]: + """locally installed packages, lazy execution""" + if self._local_packages_id is None: + self._updateLocalPackages() + # _updateLocalPackages always results in a list of packages, not None. + # It's guaranteed to be a list now. + return cast(Set[str], self._local_packages_id) + def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index e6e5e78ba9..4271166318 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -184,7 +184,6 @@ class PackageList(ListModel): to_be_installed = self._manager.installPackage(package_path) is not None package = self.getPackageModel(package_id) # 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: @@ -268,8 +267,8 @@ class PackageList(ListModel): package.installPackageTriggered.connect(self.installPackage) package.uninstallPackageTriggered.connect(self.uninstallPackage) package.updatePackageTriggered.connect(self.updatePackage) - package.enablePackageTriggered.connect(self.enablePackage) - package.disablePackageTriggered.connect(self.disablePackage) + package.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) + package.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) def installPackage(self, package_id: str) -> None: """Install a package from the Marketplace @@ -288,7 +287,6 @@ class PackageList(ListModel): package = self.getPackageModel(package_id) self._manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) - package.isRecentlyInstalledChanged.emit(False) def updatePackage(self, package_id: str) -> None: """Update a package from the Marketplace @@ -299,21 +297,3 @@ class PackageList(ListModel): self._manager.removePackage(package_id, force_add = True) url = package.download_url self.download(package_id, url, True) - - def enablePackage(self, package_id: str) -> None: - """Enable a package in the plugin registry - - :param package_id: the package identification string - """ - package = self.getPackageModel(package_id) - self._plugin_registry.enablePlugin(package_id) - package.is_active = True - - def disablePackage(self, package_id: str) -> None: - """Disable a package in the plugin registry - - :param package_id: the package identification string - """ - package = self.getPackageModel(package_id) - self._plugin_registry.disablePlugin(package_id) - package.is_active = False diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 9996a8e1df..6d20cd7f91 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -3,22 +3,21 @@ import re from enum import Enum -from typing import Any, Dict, List, Optional +from typing import Any, cast, Dict, List, Optional from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal from cura.CuraApplication import CuraApplication +from cura.CuraPackageManager import CuraPackageManager from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. +from UM.PluginRegistry import PluginRegistry catalog = i18nCatalog("cura") class PackageModel(QObject): """ Represents a package, containing all the relevant information to be displayed about a package. - - Effectively this behaves like a glorified named tuple, but as a QObject so that its properties can be obtained from - QML. The model can also be constructed directly from a response received by the API. """ def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None: @@ -29,10 +28,11 @@ class PackageModel(QObject): :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ super().__init__(parent) + self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() + self._package_id = package_data.get("package_id", "UnknownPackageId") self._package_type = package_data.get("package_type", "") - self._is_installed = package_data.get("is_installed", False) - self._is_active = package_data.get("is_active", False) self._is_bundled = package_data.get("is_bundled", False) self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) @@ -89,13 +89,11 @@ class PackageModel(QObject): self.updatePackageTriggered.connect(update_clicked) - def finished_installed(is_updating): - if is_updating: - self.setIsUpdating(False) - else: - self.setIsInstalling(False) + def finished_installed(): + self.setIsUpdating(False) + self.setIsInstalling(False) - self.isRecentlyInstalledChanged.connect(finished_installed) + self._package_manager.installedPackagesChanged.connect(finished_installed) def __eq__(self, other: object): if isinstance(other, PackageModel): @@ -313,30 +311,9 @@ class PackageModel(QObject): isRecentlyInstalledChanged = pyqtSignal(bool) - # --- enabling --- - - @pyqtProperty(bool, notify = stateManageButtonChanged) - def stateManageEnableButton(self) -> bool: - """The state of the manage Enable Button of this package""" - return not (self._is_installed and self._is_active) - - @property - def is_active(self) -> bool: - """Flag if the package is currently active""" - 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(bool, notify = stateManageButtonChanged) - def stateManageInstallButton(self) -> bool: - """The state of the Manage Install package card""" - return not self._is_installed + def isActive(self): + return not self._package_id in self._plugin_registry.getDisabledPlugins() def setIsInstalling(self, value: bool) -> None: if value != self._is_installing: @@ -358,15 +335,15 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in CuraApplication.getInstance().getPackageManager().local_packages + return self._package_id in self._package_manager.local_packages_id @pyqtProperty(bool, notify = stateManageButtonChanged) def isRecentlyInstalled(self) -> bool: - return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() or self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() + return self._package_id in self._package_manager.getPackagesToInstall() @pyqtProperty(bool, notify = stateManageButtonChanged) - def isUninstalled(self) -> bool: - return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() + def isRecentlyUninstalled(self) -> bool: + return self._package_id in self._package_manager.getPackagesToRemove() def setCanDowngrade(self, value: bool) -> None: if value != self._can_downgrade: @@ -378,8 +355,6 @@ class PackageModel(QObject): """Flag if the installed package can be downgraded to a bundled version""" return self._can_downgrade - # --- Updating --- - def setIsUpdating(self, value): if value != self._is_updating: self._is_updating = value @@ -391,7 +366,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isRecentlyUpdated(self): - return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() and self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() + return self._package_id in self._package_manager.getPackagesToInstall() and self._package_id in self._package_manager.getPackagesToRemove() @property def can_update(self) -> bool: diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 5859fbcd8e..4964338ab2 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -187,7 +187,7 @@ Item busy: false confirmed: false - button_style: packageData.stateManageEnableButton + button_style: packageData.isInstalled && packageData.isActive Layout.alignment: Qt.AlignTop text: packageData.stateManageEnableButton ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") @@ -213,23 +213,23 @@ Item enabled: !packageData.isUpdating busy: packageData.isInstalling - confirmed: packageData.isRecentlyInstalled + confirmed: packageData.isRecentlyInstalled || packageData.isRecentlyUninstalled - button_style: packageData.stateManageInstallButton + button_style: !packageData.isInstalled Layout.alignment: Qt.AlignTop text: { - if (packageData.stateManageInstallButton) + if (packageData.isRecentlyInstalled) { return catalog.i18nc("@button", "Installed"); } + if (packageData.isRecentlyUninstalled) { return catalog.i18nc("@button", "Uninstalled"); } + if (button_style) { if (packageData.isInstalling) { return catalog.i18nc("@button", "Installing..."); } - else if (packageData.isRecentlyInstalled) { 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"); } } } -- cgit v1.2.3 From 8dc88e52c2d8321ce0e0607ff3fcfadf2a392506 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 10:03:56 +0100 Subject: Don't show the Enable buttons for materials Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 4964338ab2..ee7745235f 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && packageData.isInstalled && !(installManageButton.confirmed || updateManageButton.confirmed) + visible: showManageButtons && packageData.isInstalled && !(installManageButton.confirmed || updateManageButton.confirmed || packageData.packageType == "material") enabled: !(installManageButton.busy || updateManageButton.busy) busy: false -- cgit v1.2.3 From 0299bb1694f7f5c21a1d1a61101d5e83252b5145 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 10:24:26 +0100 Subject: Fixed update buttons Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 6 +++--- plugins/Marketplace/PackageModel.py | 13 ++++++------- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 32e60b2518..72199bd0e4 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -51,7 +51,7 @@ class LocalPackageList(PackageList): # Obtain and sort the local packages self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.local_packages]]) - self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) + self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True) self.checkForUpdates(self._manager.local_packages) self.setIsLoading(False) @@ -97,9 +97,9 @@ 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.can_update = True + package.setCanUpdate(True) - self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) + self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True) self._ongoing_requests["check_updates"] = None 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/PackageModel.py b/plugins/Marketplace/PackageModel.py index 6d20cd7f91..f4afaf0a68 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -368,13 +368,12 @@ class PackageModel(QObject): def isRecentlyUpdated(self): return self._package_id in self._package_manager.getPackagesToInstall() and self._package_id in self._package_manager.getPackagesToRemove() - @property - def can_update(self) -> bool: - """Flag indicating if the package can be updated""" - return self._can_update - - @can_update.setter - def can_update(self, value: bool) -> None: + def setCanUpdate(self, value: bool) -> None: if value != self._can_update: self._can_update = value self.stateManageButtonChanged.emit() + + @pyqtProperty(bool, fset = setCanUpdate, notify = stateManageButtonChanged) + def canUpdate(self) -> bool: + """Flag indicating if the package can be updated""" + return self._can_update diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index ee7745235f..59a4408ce7 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -250,7 +250,7 @@ Item ManageButton { id: updateManageButton - visible: (showManageButtons && confirmed) && !installManageButton.confirmed + visible: (showManageButtons || confirmed) && packageData.canUpdate && !installManageButton.confirmed enabled: !installManageButton.busy busy: packageData.isUpdating -- cgit v1.2.3 From 8a583a43238e9af5d4dcca554a83c88c1b227b09 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 10:27:27 +0100 Subject: Fixed Enable button text and style Contributes to: CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 59a4408ce7..be0a52f52a 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -187,10 +187,10 @@ Item busy: false confirmed: false - button_style: packageData.isInstalled && packageData.isActive + button_style: packageData.isInstalled && !packageData.isActive Layout.alignment: Qt.AlignTop - text: packageData.stateManageEnableButton ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") + text: button_style ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") onClicked: { -- cgit v1.2.3 From 4c5ca22b24c57a5c2ad6ce9493573dc0f17a955c Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 12:04:47 +0100 Subject: Handle bundled packages which can be Downgraded Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 1 - plugins/Marketplace/PackageModel.py | 12 ++---------- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 6 +++++- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 72199bd0e4..7bd2005fd2 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -66,7 +66,6 @@ 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.setCanDowngrade(self._manager.canDowngrade(package_id)) return package def checkForUpdates(self, packages: List[Dict[str, Any]]): diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index f4afaf0a68..14a71206d9 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -67,7 +67,6 @@ class PackageModel(QObject): self._can_update = False 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. @@ -309,8 +308,6 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) - isRecentlyInstalledChanged = pyqtSignal(bool) - @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self): return not self._package_id in self._plugin_registry.getDisabledPlugins() @@ -345,15 +342,10 @@ class PackageModel(QObject): def isRecentlyUninstalled(self) -> bool: return self._package_id in self._package_manager.getPackagesToRemove() - def setCanDowngrade(self, value: bool) -> None: - if value != self._can_downgrade: - self._can_downgrade = value - self.stateManageButtonChanged.emit() - - @pyqtProperty(bool, fset = setCanDowngrade, notify = stateManageButtonChanged) + @pyqtProperty(bool, notify = stateManageButtonChanged) def canDowngrade(self) -> bool: """Flag if the installed package can be downgraded to a bundled version""" - return self._can_downgrade + return self._package_manager.canDowngrade(self._package_id) def setIsUpdating(self, value): if value != self._is_updating: diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index be0a52f52a..ff999c1ee6 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -220,8 +220,12 @@ Item text: { + if (packageData.canDowngrade) { return catalog.i18nc("@button", "Downgrade"); } if (packageData.isRecentlyInstalled) { return catalog.i18nc("@button", "Installed"); } - if (packageData.isRecentlyUninstalled) { return catalog.i18nc("@button", "Uninstalled"); } + if (packageData.isRecentlyUninstalled) + { + if (packageData.canDowngrade) { return catalog.i18nc("@button", "Downgraded") } + else { return catalog.i18nc("@button", "Uninstalled"); } } if (button_style) { if (packageData.isInstalling) { return catalog.i18nc("@button", "Installing..."); } -- cgit v1.2.3 From 9874b0c8ba9d72136bd5be18361fc7bc61ca1a32 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 15:14:20 +0100 Subject: removed redundant dunders from helper class Contributes to: CURA-8587 --- cura/CuraPackageManager.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 386d249925..fcbf5f6ea6 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -78,23 +78,11 @@ class CuraPackageManager(PackageManager): def getAllLocalPackages(self) -> List[Dict[str, Any]]: """ returns an unordered list of all the package_info installed, to be installed or to be returned""" - class PkgInfo: - # Needed helper class because a dict isn't hashable - def __init__(self, package_info): - self._info = package_info + class PkgInfo(dict): + # Needed helper class because a dict isn't hashable def __eq__(self, item): - return item == self._info["package_id"] - - def __repr__(self): - return repr(self._info) - - def __iter__(self): - for k, v in self._info.items(): - yield k, v - - def asdict(self): - return self._info + return item == self["package_id"] packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToRemove().values() if package["package_info"]["package_id"] not in packages]) -- cgit v1.2.3 From 1adae61f39d17f1f9833324c74728314aed9bdb7 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 9 Dec 2021 15:49:05 +0100 Subject: Enable and disabled now toggle correctly Contributes to: CURA-8587 --- plugins/Marketplace/PackageList.py | 2 -- plugins/Marketplace/PackageModel.py | 5 +++++ plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 4271166318..8ddd74ead9 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -267,8 +267,6 @@ class PackageList(ListModel): package.installPackageTriggered.connect(self.installPackage) package.uninstallPackageTriggered.connect(self.uninstallPackage) package.updatePackageTriggered.connect(self.updatePackage) - package.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) - package.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) def installPackage(self, package_id: str) -> None: """Install a package from the Marketplace diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 14a71206d9..aa63d6da13 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -11,6 +11,7 @@ from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. +from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry catalog = i18nCatalog("cura") @@ -93,6 +94,9 @@ class PackageModel(QObject): self.setIsInstalling(False) self._package_manager.installedPackagesChanged.connect(finished_installed) + self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) + self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) + self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) def __eq__(self, other: object): if isinstance(other, PackageModel): @@ -310,6 +314,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self): + Logger.debug(f"getDisabledPlugins = {self._plugin_registry.getDisabledPlugins()}") return not self._package_id in self._plugin_registry.getDisabledPlugins() def setIsInstalling(self, value: bool) -> None: diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index ff999c1ee6..5a693ece83 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -187,7 +187,7 @@ Item busy: false confirmed: false - button_style: packageData.isInstalled && !packageData.isActive + button_style: !packageData.isActive Layout.alignment: Qt.AlignTop text: button_style ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") -- cgit v1.2.3 From 4991c3953520709b2b1d8d55512a57d457778fb9 Mon Sep 17 00:00:00 2001 From: casper Date: Thu, 9 Dec 2021 22:41:14 +0100 Subject: Make sure `ManageButton` has the correct width and height Cura 8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 26 ++++++---------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index fdeb37a87a..2c05af02e9 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -16,19 +16,17 @@ Item property bool busy property bool confirmed + Layout.preferredWidth: childrenRect.width + Layout.preferredHeight: childrenRect.height + signal clicked(bool primary_action) property Component primaryButton: Component { Cura.PrimaryButton { - id: primaryButton text: manageButton.text - - onClicked: - { - manageButton.clicked(true) - } + onClicked: manageButton.clicked(true) } } @@ -36,13 +34,8 @@ Item { Cura.SecondaryButton { - id: secondaryButton text: manageButton.text - - onClicked: - { - manageButton.clicked(false) - } + onClicked: manageButton.clicked(false) } } @@ -50,7 +43,8 @@ Item { Item { - id: busyMessage + height: UM.Theme.getSize("action_button").height + width: childrenRect.width UM.RecolorImage { @@ -76,7 +70,6 @@ Item } Label { - id: busyMessageText visible: parent.visible anchors.left: busyIndicator.right anchors.leftMargin: UM.Theme.getSize("narrow_margin").width @@ -93,13 +86,11 @@ Item { Item { - height: UM.Theme.getSize("action_button").height width: childrenRect.width Label { - id: confirmedMessageText anchors.verticalCenter: parent.verticalCenter text: manageButton.text @@ -109,9 +100,6 @@ Item } } - height: UM.Theme.getSize("action_button").height - width: childrenRect.width - Loader { -- cgit v1.2.3 From d9f77d7ffda2ad13759d97d0eba5a4a0e9145541 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 11:14:40 +0100 Subject: Moved the update logic to the PackageManager Contributes to CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 21 +++------- plugins/Marketplace/PackageList.py | 12 ++---- plugins/Marketplace/PackageModel.py | 47 +++++++--------------- .../resources/qml/PackageCardHeader.qml | 8 ++-- 4 files changed, 29 insertions(+), 59 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 7bd2005fd2..83aac65516 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,5 +1,5 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. from typing import Any, Dict, List, Optional, TYPE_CHECKING from operator import attrgetter @@ -39,6 +39,7 @@ class LocalPackageList(PackageList): super().__init__(parent) self._has_footer = False self._ongoing_requests["check_updates"] = None + self._manager.packagesWithUpdateChanged.connect(lambda: self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True)) @pyqtSlot() def updatePackages(self) -> None: @@ -92,16 +93,6 @@ class LocalPackageList(PackageList): if len(response_data["data"]) == 0: return - try: - for package_data in response_data["data"]: - package = self.getPackageModel(package_data["package_id"]) - package.download_url = package_data.get("download_url", "") - package.setCanUpdate(True) - - self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True) - self._ongoing_requests["check_updates"] = None - 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 RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object - # was deleted when it was still parsing the response - return + packages = response_data["data"] + self._manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) + self._ongoing_requests["check_updates"] = None diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 8ddd74ead9..c245b44df9 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -1,5 +1,5 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. import tempfile import json import os.path @@ -268,13 +268,11 @@ class PackageList(ListModel): package.uninstallPackageTriggered.connect(self.uninstallPackage) package.updatePackageTriggered.connect(self.updatePackage) - def installPackage(self, package_id: str) -> None: + def installPackage(self, package_id: str, url: str) -> None: """Install a package from the Marketplace :param package_id: the package identification string """ - package = self.getPackageModel(package_id) - url = package.download_url self.download(package_id, url, False) def uninstallPackage(self, package_id: str) -> None: @@ -282,7 +280,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ - package = self.getPackageModel(package_id) self._manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) @@ -291,7 +288,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ - package = self.getPackageModel(package_id) self._manager.removePackage(package_id, force_add = True) - url = package.download_url + url = self._manager.packagesWithUpdate[package_id]["download_url"] self.download(package_id, url, True) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index aa63d6da13..e2d785606d 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -1,5 +1,5 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. import re from enum import Enum @@ -45,7 +45,7 @@ class PackageModel(QObject): self._description = package_data.get("description", "") self._formatted_description = self._format(self._description) - self.download_url = package_data.get("download_url", "") + self._download_url = package_data.get("download_url", "") self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description? subdata = package_data.get("data", {}) @@ -72,32 +72,21 @@ class PackageModel(QObject): 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) + self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) + self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) + self.installPackageTriggered.connect(lambda pkg_id: self.setIsInstalling(True)) + self.uninstallPackageTriggered.connect(lambda pkg_id: self.setIsInstalling(True)) + self.updatePackageTriggered.connect(lambda pkg_id: self.setIsUpdating(True)) def finished_installed(): self.setIsUpdating(False) self.setIsInstalling(False) self._package_manager.installedPackagesChanged.connect(finished_installed) - self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) - self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) + self._package_manager.packagesWithUpdateChanged.connect(lambda : self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) + def __eq__(self, other: object): if isinstance(other, PackageModel): return other == self @@ -298,11 +287,15 @@ class PackageModel(QObject): def isBundled(self) -> bool: return self._is_bundled + @pyqtProperty(str, constant = True) + def downloadURL(self) -> str: + return self._download_url + # --- manage buttons signals --- stateManageButtonChanged = pyqtSignal() - installPackageTriggered = pyqtSignal(str) + installPackageTriggered = pyqtSignal(str, str) uninstallPackageTriggered = pyqtSignal(str) @@ -314,7 +307,6 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self): - Logger.debug(f"getDisabledPlugins = {self._plugin_registry.getDisabledPlugins()}") return not self._package_id in self._plugin_registry.getDisabledPlugins() def setIsInstalling(self, value: bool) -> None: @@ -326,15 +318,6 @@ class PackageModel(QObject): def isInstalling(self) -> bool: return self._is_installing - 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 isInstalled(self) -> bool: return self._package_id in self._package_manager.local_packages_id diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 5a693ece83..ec33505391 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -254,7 +254,7 @@ Item ManageButton { id: updateManageButton - visible: (showManageButtons || confirmed) && packageData.canUpdate && !installManageButton.confirmed + visible: (showManageButtons || confirmed) && (packageData.canUpdate || confirmed) && !installManageButton.confirmed enabled: !installManageButton.busy busy: packageData.isUpdating @@ -265,12 +265,12 @@ Item text: { - if (packageData.isUpdating) { return catalog.i18nc("@button", "Updating..."); } - else if (packageData.isRecentlyUpdated) { return catalog.i18nc("@button", "Updated"); } + if (busy) { return catalog.i18nc("@button", "Updating..."); } + else if (confirmed) { return catalog.i18nc("@button", "Updated"); } else { return catalog.i18nc("@button", "Update"); } } - onClicked: packageData.updatePackageTriggered(packageData.packageId) + onClicked: packageData.updatePackageTriggered(packageData.packageId, packageData.downloadURL) } } } -- cgit v1.2.3 From e0754c7015be45a652274f237bd0e8517d752bcc Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 11:16:08 +0100 Subject: Disabled old toolbox functionality for updating The signals messed up the new update process. Contributes to CURA-8587 and relates to CURA-8588 --- plugins/Toolbox/src/Toolbox.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index e525a88d89..d0bfb0efd4 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -1,5 +1,5 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Toolbox is released under the terms of the LGPLv3 or higher. +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. import json import os @@ -634,8 +634,8 @@ class Toolbox(QObject, Extension): self._models[request_type].setFilter({"tags": "generic"}) elif request_type == "updates": # Tell the package manager that there's a new set of updates available. - packages = set([pkg["package_id"] for pkg in self._server_response_data[request_type]]) - self._package_manager.setPackagesWithUpdate(packages) + packages = self._server_response_data[request_type] + # self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) self.metadataChanged.emit() -- cgit v1.2.3 From ec8254bca969be904a7c731845d2b435f671e038 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 11:50:02 +0100 Subject: Fixed old Marketplace update functionality Contributes to CURA-8587 --- plugins/Toolbox/src/Toolbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index d0bfb0efd4..5644bace7a 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -635,7 +635,7 @@ class Toolbox(QObject, Extension): elif request_type == "updates": # Tell the package manager that there's a new set of updates available. packages = self._server_response_data[request_type] - # self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) + self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) self.metadataChanged.emit() @@ -645,7 +645,7 @@ class Toolbox(QObject, Extension): # This function goes through all known remote versions of a package and notifies the package manager of this change def _notifyPackageManager(self): for package in self._server_response_data["packages"]: - self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"])) + self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]), package) def _onDownloadFinished(self, reply: "QNetworkReply") -> None: self.resetDownload() -- cgit v1.2.3 From 9b7731a21be9d11dbe0df4243fdb160df7376d12 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 11:50:38 +0100 Subject: Use package_infos to obtain the url Contributes to CURA-8587 --- 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 c245b44df9..9d1fb08488 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -289,5 +289,5 @@ class PackageList(ListModel): :param package_id: the package identification string """ self._manager.removePackage(package_id, force_add = True) - url = self._manager.packagesWithUpdate[package_id]["download_url"] + url = self._manager.package_infosWithUpdate[package_id]["download_url"] self.download(package_id, url, True) -- cgit v1.2.3 From a571e875530746c4b728312f0fae07a1c673a7a7 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 12:39:44 +0100 Subject: Fixed the spinner Contributes to CURA-8587 --- plugins/Marketplace/PackageModel.py | 17 ++++++++++++----- plugins/Marketplace/resources/qml/ManageButton.qml | 2 +- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index e2d785606d..6a7c943e2c 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -64,23 +64,30 @@ class PackageModel(QObject): self._icon_url = author_data.get("icon_url", "") self._is_installing = False + self._recently_installed = False self._install_status_changing = False self._can_update = False self._is_updating = False + self._recently_updated = 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. self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) - self.installPackageTriggered.connect(lambda pkg_id: self.setIsInstalling(True)) + self.installPackageTriggered.connect(lambda pkg_id, url: self.setIsInstalling(True)) self.uninstallPackageTriggered.connect(lambda pkg_id: self.setIsInstalling(True)) self.updatePackageTriggered.connect(lambda pkg_id: self.setIsUpdating(True)) def finished_installed(): - self.setIsUpdating(False) - self.setIsInstalling(False) + if self.isInstalling: + self._recently_installed = True + self.setIsInstalling(False) + if self.isUpdating: + self._recently_updated = True + self._can_update = not self._package_id in self._package_manager.getPackagesToInstall() + self.setIsUpdating(False) self._package_manager.installedPackagesChanged.connect(finished_installed) self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) @@ -324,7 +331,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isRecentlyInstalled(self) -> bool: - return self._package_id in self._package_manager.getPackagesToInstall() + return self._recently_installed and self._package_id in self._package_manager.getPackagesToInstall() @pyqtProperty(bool, notify = stateManageButtonChanged) def isRecentlyUninstalled(self) -> bool: @@ -346,7 +353,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isRecentlyUpdated(self): - return self._package_id in self._package_manager.getPackagesToInstall() and self._package_id in self._package_manager.getPackagesToRemove() + return self._recently_updated and self._package_id in self._package_manager.getPackagesToInstall() def setCanUpdate(self, value: bool) -> None: if value != self._can_update: diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 2c05af02e9..ac7577b543 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -61,7 +61,7 @@ Item RotationAnimator { target: busyIndicator - running: busyMessage.visible + running: parent.visible from: 0 to: 360 loops: Animation.Infinite diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index ec33505391..56cfe4fa61 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -242,7 +242,7 @@ Item { if (primary_action) { - packageData.installPackageTriggered(packageData.packageId) + packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL) } else { @@ -270,7 +270,7 @@ Item else { return catalog.i18nc("@button", "Update"); } } - onClicked: packageData.updatePackageTriggered(packageData.packageId, packageData.downloadURL) + onClicked: packageData.updatePackageTriggered(packageData.packageId) } } } -- cgit v1.2.3 From 11c2ccd227b4971e5657b62d2c089643469821a8 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 12:41:37 +0100 Subject: Use the ongoing_request queue Contributes to CURA-8587 --- plugins/Marketplace/RemotePackageList.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 5325fc8640..7cbd00ad76 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -1,5 +1,5 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkReply @@ -131,7 +131,7 @@ class RemotePackageList(PackageList): return self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. - self._ongoing_request = None + self._ongoing_requests["get_packages"] = None self.setIsLoading(False) self.setHasMore(self._request_url != "") @@ -143,9 +143,9 @@ class RemotePackageList(PackageList): """ if error == QNetworkReply.NetworkError.OperationCanceledError: Logger.debug("Cancelled request for packages.") - self._ongoing_request = None + self._ongoing_requests["get_packages"] = 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._ongoing_requests["get_packages"] = None self.setIsLoading(False) -- cgit v1.2.3 From d876b85259a5346dbcadb9679a954be94dd81bed Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 12:43:42 +0100 Subject: Don't forcefully remove bundled packages when updating Contributes to CURA-8587 --- 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 9d1fb08488..ca3d4aff41 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -288,6 +288,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ - self._manager.removePackage(package_id, force_add = True) + self._manager.removePackage(package_id, force_add = not self._manager.isBundledPackage(package_id)) url = self._manager.package_infosWithUpdate[package_id]["download_url"] self.download(package_id, url, True) -- cgit v1.2.3 From e72655cc22b0bb8f27aae82afcf6c2ee1298114e Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 17:38:20 +0100 Subject: Moved busy/confirmed logic to QML COntributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 14 +- plugins/Marketplace/PackageModel.py | 145 +++++++++++---------- .../resources/qml/PackageCardHeader.qml | 74 ++++++++--- 3 files changed, 134 insertions(+), 99 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index ca3d4aff41..390bf841df 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -165,7 +165,7 @@ class PackageList(ListModel): if dialog is not None: dialog.deleteLater() # reset package card - package = self.getPackageModel(package_id) + self._manager.packageInstallingFailed.emit(package_id) def _requestInstall(self, package_id: str, update: bool = False) -> None: package_path = self._to_install[package_id] @@ -182,8 +182,9 @@ class PackageList(ListModel): def _install(self, package_id: str, update: bool = False) -> None: package_path = self._to_install.pop(package_id) to_be_installed = self._manager.installPackage(package_path) is not None + if not to_be_installed: + return package = self.getPackageModel(package_id) - # TODO handle failure self.subscribeUserToPackage(package_id, str(package.sdk_version)) def download(self, package_id: str, url: str, update: bool = False) -> None: @@ -231,14 +232,7 @@ class PackageList(ListModel): if reply: reply_string = bytes(reply.readAll()).decode() Logger.error(f"Failed to download package: {package_id} due to {reply_string}") - try: - package = self.getPackageModel(package_id) - # 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 - # was deleted when it was still parsing the response - return + self._manager.packageInstallingFailed.emit(package_id) def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: """Subscribe the user (if logged in) to the package for a given SDK diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 6a7c943e2c..de2732f147 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -16,6 +16,7 @@ from UM.PluginRegistry import PluginRegistry catalog = i18nCatalog("cura") + class PackageModel(QObject): """ Represents a package, containing all the relevant information to be displayed about a package. @@ -38,7 +39,8 @@ class PackageModel(QObject): self._icon_url = package_data.get("icon_url", "") self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package")) tags = package_data.get("tags", []) - self._is_checked_by_ultimaker = (self._package_type == "plugin" and "verified" in tags) or (self._package_type == "material" and "certified" in tags) + self._is_checked_by_ultimaker = (self._package_type == "plugin" and "verified" in tags) or ( + self._package_type == "material" and "certified" in tags) self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'. self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'. self._download_count = package_data.get("download_count", 0) @@ -63,38 +65,32 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") - self._is_installing = False - self._recently_installed = False - self._install_status_changing = False - self._can_update = False - self._is_updating = False - self._recently_updated = 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. self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) - self.installPackageTriggered.connect(lambda pkg_id, url: self.setIsInstalling(True)) - self.uninstallPackageTriggered.connect(lambda pkg_id: self.setIsInstalling(True)) - self.updatePackageTriggered.connect(lambda pkg_id: self.setIsUpdating(True)) - - def finished_installed(): - if self.isInstalling: - self._recently_installed = True - self.setIsInstalling(False) - if self.isUpdating: - self._recently_updated = True - self._can_update = not self._package_id in self._package_manager.getPackagesToInstall() - self.setIsUpdating(False) - - self._package_manager.installedPackagesChanged.connect(finished_installed) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) - self._package_manager.packagesWithUpdateChanged.connect(lambda : self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) + self._is_updating = False + self._is_recently_updated = self._getRecentlyUpdated() + + self._is_installing = False + self._is_uninstalling = False + self._is_recently_installed = self._getRecentlyInstalled() - def __eq__(self, other: object): + self.updatePackageTriggered.connect(lambda pkg: self._setIsUpdating(True)) + self.installPackageTriggered.connect(lambda pkg, url: self._setIsInstalling(True)) + self.uninstallPackageTriggered.connect(lambda pkg: self._setIsUninstalling(True)) + + self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) + self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id, True)) + self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id, True)) + self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id, False)) + self._package_manager.packagesWithUpdateChanged.connect(lambda: self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) + + def __eq__(self, other: object) -> bool: if isinstance(other, PackageModel): return other == self elif isinstance(other, str): @@ -102,7 +98,7 @@ class PackageModel(QObject): else: return False - def __repr__(self): + def __repr__(self) -> str: return f"<{self._package_id} : {self._package_version} : {self._section_title}>" def _findLink(self, subdata: Dict[str, Any], link_type: str) -> str: @@ -218,8 +214,8 @@ class PackageModel(QObject): def packageType(self) -> str: return self._package_type - @pyqtProperty(str, constant=True) - def iconUrl(self): + @pyqtProperty(str, constant = True) + def iconUrl(self) -> str: return self._icon_url @pyqtProperty(str, constant = True) @@ -230,32 +226,32 @@ class PackageModel(QObject): def isCheckedByUltimaker(self): return self._is_checked_by_ultimaker - @pyqtProperty(str, constant=True) - def packageVersion(self): + @pyqtProperty(str, constant = True) + def packageVersion(self) -> str: return self._package_version - @pyqtProperty(str, constant=True) - def packageInfoUrl(self): + @pyqtProperty(str, constant = True) + def packageInfoUrl(self) -> str: return self._package_info_url - @pyqtProperty(int, constant=True) - def downloadCount(self): + @pyqtProperty(int, constant = True) + def downloadCount(self) -> str: return self._download_count - @pyqtProperty(str, constant=True) - def description(self): + @pyqtProperty(str, constant = True) + def description(self) -> str: return self._description @pyqtProperty(str, constant = True) def formattedDescription(self) -> str: return self._formatted_description - @pyqtProperty(str, constant=True) - def authorName(self): + @pyqtProperty(str, constant = True) + def authorName(self) -> str: return self._author_name - @pyqtProperty(str, constant=True) - def authorInfoUrl(self): + @pyqtProperty(str, constant = True) + def authorInfoUrl(self) -> str: return self._author_info_url @pyqtProperty(str, constant = True) @@ -312,49 +308,62 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) - @pyqtProperty(bool, notify = stateManageButtonChanged) - def isActive(self): - return not self._package_id in self._plugin_registry.getDisabledPlugins() + installedPackagesChanged = pyqtSignal(bool) - def setIsInstalling(self, value: bool) -> None: - if value != self._is_installing: - self._is_installing = value - self.stateManageButtonChanged.emit() + uninstalledPackagesChanged = pyqtSignal(bool) + + updatePackagesChanged = pyqtSignal(bool) + + def _setIsUpdating(self, value: bool) -> None: + self._is_updating = value + + def _setIsInstalling(self, value: bool) -> None: + self._is_installing = value + + def _setIsUninstalling(self, value: bool) -> None: + self._is_uninstalling = value - @pyqtProperty(bool, fset = setIsInstalling, notify = stateManageButtonChanged) - def isInstalling(self) -> bool: - return self._is_installing + def _packageInstalled(self, package_id: str, success: bool) -> None: + if self._package_id != package_id: + return + if self._is_updating: + self.updatePackagesChanged.emit(success) + self._is_updating = False + if self._is_installing: + self.installedPackagesChanged.emit(success) + self._is_installing = False + if self._is_uninstalling: + self.uninstalledPackagesChanged.emit(success) + self._is_uninstalling = False + + def _getRecentlyInstalled(self) -> bool: + return (self._package_id in self._package_manager.getPackagesToInstall() or self._package_id in self._package_manager.getPackagesToRemove()) \ + and self._package_id not in self._package_manager.package_infosWithUpdate + + @pyqtProperty(bool, constant = True) + def isRecentlyInstalledChanged(self) -> bool: + return self._is_recently_installed + + def _getRecentlyUpdated(self) -> bool: + return self._package_id in self._package_manager.package_infosWithUpdate and self._package_id in self._package_manager.getPackagesToInstall() + + @pyqtProperty(bool, constant = True) + def isRecentlyUpdatedChanged(self) -> bool: + return self._is_recently_updated @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: return self._package_id in self._package_manager.local_packages_id @pyqtProperty(bool, notify = stateManageButtonChanged) - def isRecentlyInstalled(self) -> bool: - return self._recently_installed and self._package_id in self._package_manager.getPackagesToInstall() - - @pyqtProperty(bool, notify = stateManageButtonChanged) - def isRecentlyUninstalled(self) -> bool: - return self._package_id in self._package_manager.getPackagesToRemove() + def isActive(self) -> bool: + return not self._package_id in self._plugin_registry.getDisabledPlugins() @pyqtProperty(bool, notify = stateManageButtonChanged) def canDowngrade(self) -> bool: """Flag if the installed package can be downgraded to a bundled version""" return self._package_manager.canDowngrade(self._package_id) - def setIsUpdating(self, value): - if value != self._is_updating: - self._is_updating = value - self.stateManageButtonChanged.emit() - - @pyqtProperty(bool, fset = setIsUpdating, notify = stateManageButtonChanged) - def isUpdating(self): - return self._is_updating - - @pyqtProperty(bool, notify = stateManageButtonChanged) - def isRecentlyUpdated(self): - return self._recently_updated and self._package_id in self._package_manager.getPackagesToInstall() - def setCanUpdate(self, value: bool) -> None: if value != self._can_update: self._can_update = value diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 56cfe4fa61..627fc27d46 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -209,45 +209,62 @@ Item { id: installManageButton visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled) && !updateManageButton.confirmed + enabled: !updateManageButton.busy - enabled: !packageData.isUpdating - - busy: packageData.isInstalling - confirmed: packageData.isRecentlyInstalled || packageData.isRecentlyUninstalled + busy: false + confirmed: { packageData.isRecentlyInstalledChanged } - button_style: !packageData.isInstalled + button_style: confirmed ? packageData.isInstalled : !packageData.isInstalled Layout.alignment: Qt.AlignTop text: { - if (packageData.canDowngrade) { return catalog.i18nc("@button", "Downgrade"); } - if (packageData.isRecentlyInstalled) { return catalog.i18nc("@button", "Installed"); } - if (packageData.isRecentlyUninstalled) - { - if (packageData.canDowngrade) { return catalog.i18nc("@button", "Downgraded") } - else { return catalog.i18nc("@button", "Uninstalled"); } } if (button_style) { - if (packageData.isInstalling) { return catalog.i18nc("@button", "Installing..."); } + if (busy) { return catalog.i18nc("@button", "Installing..."); } + else if (confirmed) { return catalog.i18nc("@button", "Installed"); } else { return catalog.i18nc("@button", "Install"); } } else { - if (packageData.isInstalling) { return catalog.i18nc("@button", "Uninstalling..."); } - else { return catalog.i18nc("@button", "Uninstall"); } + if (packageData.canDowngrade) + { + if (busy) { return catalog.i18nc("@button", "Downgrading..."); } + else if (confirmed) { return catalog.i18nc("@button", "Downgraded"); } + else { return catalog.i18nc("@button", "Downgrade"); } + } + else + { + if (busy) { return catalog.i18nc("@button", "Uninstalling..."); } + else if (confirmed) { return catalog.i18nc("@button", "Uninstalled"); } + else { return catalog.i18nc("@button", "Uninstall"); } + } } } onClicked: { - if (primary_action) + busy = true + if (primary_action){ packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL); } + else { packageData.uninstallPackageTriggered(packageData.packageId); } + } + + Connections + { + target: packageData + + function onInstalledPackagesChanged(success) { - packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL) + installManageButton.busy = false; + installManageButton.confirmed = success; } - else + function onUninstalledPackagesChanged(success) { - packageData.uninstallPackageTriggered(packageData.packageId) + installManageButton.busy = false; + installManageButton.confirmed = success; + installManageButton.button_style = !installManageButton.button_style; } + } } @@ -257,8 +274,8 @@ Item visible: (showManageButtons || confirmed) && (packageData.canUpdate || confirmed) && !installManageButton.confirmed enabled: !installManageButton.busy - busy: packageData.isUpdating - confirmed: packageData.isRecentlyUpdated + busy: false + confirmed: { packageData.isRecentlyUpdatedChanged } button_style: true Layout.alignment: Qt.AlignTop @@ -270,7 +287,22 @@ Item else { return catalog.i18nc("@button", "Update"); } } - onClicked: packageData.updatePackageTriggered(packageData.packageId) + onClicked: + { + busy = true; + packageData.updatePackageTriggered(packageData.packageId); + } + + Connections + { + target: packageData + + function onUpdatePackagesChanged(succes) + { + updateManageButton.busy = false; + updateManageButton.confirmed = succes; + } + } } } } -- cgit v1.2.3 From 4f997319b0a71052b97b789481c35d4c3bcfa007 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 10 Dec 2021 18:31:55 +0100 Subject: Try sorting on section, update and display name Contributes to CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 83aac65516..50673818bb 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -35,11 +35,16 @@ class LocalPackageList(PackageList): } } # The section headers to be used for the different package categories + def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._has_footer = False self._ongoing_requests["check_updates"] = None - self._manager.packagesWithUpdateChanged.connect(lambda: self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True)) + self._manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate) + + def _sortSectionsOnUpdate(self) -> None: + SECTION_ORDER = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) + self.sort(lambda model: f"{SECTION_ORDER[model.sectionTitle]}_{model._can_update}_{model.displayName}".lower(), key = "package") @pyqtSlot() def updatePackages(self) -> None: @@ -52,7 +57,7 @@ class LocalPackageList(PackageList): # Obtain and sort the local packages self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.local_packages]]) - self.sort(attrgetter("sectionTitle", "_can_update", "displayName"), key = "package", reverse = True) + self._sortSectionsOnUpdate() self.checkForUpdates(self._manager.local_packages) self.setIsLoading(False) -- cgit v1.2.3 From eb11e92637fce88d12557959fea85c1a1cf9e1af Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 11:54:13 +0100 Subject: Rename local_packages_id to local_packages_ids CURA-8587 --- cura/CuraPackageManager.py | 10 +++++----- plugins/Marketplace/PackageModel.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index fcbf5f6ea6..535d331a62 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -20,12 +20,12 @@ class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) self._local_packages: Optional[List[Dict[str, Any]]] = None - self._local_packages_id: Optional[Set[str]] = None + self._local_packages_ids: Optional[Set[str]] = None self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: self._local_packages = self.getAllLocalPackages() - self._local_packages_id = set(pkg["package_id"] for pkg in self._local_packages) + self._local_packages_ids = set(pkg["package_id"] for pkg in self._local_packages) @property def local_packages(self) -> List[Dict[str, Any]]: @@ -37,13 +37,13 @@ class CuraPackageManager(PackageManager): return cast(List[Dict[str, Any]], self._local_packages) @property - def local_packages_id(self) -> Set[str]: + def local_packages_ids(self) -> Set[str]: """locally installed packages, lazy execution""" - if self._local_packages_id is None: + if self._local_packages_ids is None: self._updateLocalPackages() # _updateLocalPackages always results in a list of packages, not None. # It's guaranteed to be a list now. - return cast(Set[str], self._local_packages_id) + return cast(Set[str], self._local_packages_ids) def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index de2732f147..0e808dd369 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -353,7 +353,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in self._package_manager.local_packages_id + return self._package_id in self._package_manager.local_packages_ids @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self) -> bool: -- cgit v1.2.3 From 50f1afa5d9ff282b68474e1d52b7be4b64771aef Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 11:57:00 +0100 Subject: Make plugin_registry protected CURA-8587 --- plugins/Marketplace/Marketplace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 5ebc7830ed..143469d82e 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -27,7 +27,7 @@ class Marketplace(Extension): def __init__(self) -> None: super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. - self.plugin_registry: Optional[PluginRegistry] = None + self._plugin_registry: Optional[PluginRegistry] = None qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") @@ -41,7 +41,7 @@ class Marketplace(Extension): If the window hadn't been loaded yet into Qt, it will be created lazily. """ if self._window is None: - self.plugin_registry = PluginRegistry.getInstance() + self._plugin_registry = PluginRegistry.getInstance() plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) if plugin_path is None: plugin_path = os.path.dirname(__file__) -- cgit v1.2.3 From e92cc584fb566bb847ca573296805318937f9a85 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 12:01:37 +0100 Subject: Add missing return type CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 50673818bb..49a08296f5 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -74,7 +74,7 @@ class LocalPackageList(PackageList): self._connectManageButtonSignals(package) return package - def checkForUpdates(self, packages: List[Dict[str, Any]]): + def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None: 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]}" -- cgit v1.2.3 From f993243d57bf030f78f8e1e8383428e990349db0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 12:08:33 +0100 Subject: Remove unneeded import CURA-8587 --- plugins/Marketplace/resources/qml/LicenseDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/LicenseDialog.qml b/plugins/Marketplace/resources/qml/LicenseDialog.qml index d44ef335eb..1c99569793 100644 --- a/plugins/Marketplace/resources/qml/LicenseDialog.qml +++ b/plugins/Marketplace/resources/qml/LicenseDialog.qml @@ -6,7 +6,6 @@ import QtQuick.Dialogs 1.1 import QtQuick.Window 2.2 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 -import QtQuick.Controls.Styles 1.4 import UM 1.6 as UM import Cura 1.6 as Cura -- cgit v1.2.3 From 08685af9de6dad4ffc9c2e21ead667ef08bcd2e5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 12:24:18 +0100 Subject: Switch ManageButton over to implicitWidth & height instead of layout This makes it a much easier to re-use component. CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index ac7577b543..d3b86a6fa9 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -16,8 +16,8 @@ Item property bool busy property bool confirmed - Layout.preferredWidth: childrenRect.width - Layout.preferredHeight: childrenRect.height + implicitWidth: childrenRect.width + implicitHeight: childrenRect.height signal clicked(bool primary_action) -- cgit v1.2.3 From 274b98f9b17d14785f72fd74d303eca60d23167f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 13:09:17 +0100 Subject: Remove unneeded "primary_action" from managebutton --- plugins/Marketplace/resources/qml/ManageButton.qml | 6 +++--- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index d3b86a6fa9..9bd1fae032 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -19,14 +19,14 @@ Item implicitWidth: childrenRect.width implicitHeight: childrenRect.height - signal clicked(bool primary_action) + signal clicked() property Component primaryButton: Component { Cura.PrimaryButton { text: manageButton.text - onClicked: manageButton.clicked(true) + onClicked: manageButton.clicked() } } @@ -35,7 +35,7 @@ Item Cura.SecondaryButton { text: manageButton.text - onClicked: manageButton.clicked(false) + onClicked: manageButton.clicked() } } diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 627fc27d46..76c7f91624 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -15,7 +15,7 @@ Item default property alias contents: contentItem.children; property var packageData - property bool showManageButtons + property bool showManageButtons: false width: parent.width height: UM.Theme.getSize("card").height @@ -194,13 +194,13 @@ Item onClicked: { - if (primary_action) + if(packageData.isActive) { - packageData.enablePackageTriggered(packageData.packageId) + packageData.disablePackageTriggered(packageData.packageId) } else { - packageData.disablePackageTriggered(packageData.packageId) + packageData.enablePackageTriggered(packageData.packageId) } } } @@ -245,7 +245,7 @@ Item onClicked: { busy = true - if (primary_action){ packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL); } + if (packageData.isInstalled){ packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL); } else { packageData.uninstallPackageTriggered(packageData.packageId); } } -- cgit v1.2.3 From 8c0d4899ede824c1cb9b2cbd35f93a9606c11cea Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 13:12:06 +0100 Subject: Remove unneeded {} CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 76c7f91624..df136c88ec 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -212,7 +212,7 @@ Item enabled: !updateManageButton.busy busy: false - confirmed: { packageData.isRecentlyInstalledChanged } + confirmed: packageData.isRecentlyInstalledChanged button_style: confirmed ? packageData.isInstalled : !packageData.isInstalled Layout.alignment: Qt.AlignTop @@ -275,7 +275,7 @@ Item enabled: !installManageButton.busy busy: false - confirmed: { packageData.isRecentlyUpdatedChanged } + confirmed: packageData.isRecentlyUpdatedChanged button_style: true Layout.alignment: Qt.AlignTop -- cgit v1.2.3 From aa291144701583b67893b7ff019be1fb9c1fa5de Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 13:13:42 +0100 Subject: Correctly set defaults of managebutton CURA-8587 --- plugins/Marketplace/resources/qml/ManageButton.qml | 4 ++-- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 9bd1fae032..87c72d8710 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -13,8 +13,8 @@ Item id: manageButton property bool button_style property string text - property bool busy - property bool confirmed + property bool busy: false + property bool confirmed: false implicitWidth: childrenRect.width implicitHeight: childrenRect.height diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index df136c88ec..51e6f83079 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -184,9 +184,6 @@ Item visible: showManageButtons && packageData.isInstalled && !(installManageButton.confirmed || updateManageButton.confirmed || packageData.packageType == "material") enabled: !(installManageButton.busy || updateManageButton.busy) - busy: false - confirmed: false - button_style: !packageData.isActive Layout.alignment: Qt.AlignTop @@ -211,7 +208,6 @@ Item visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled) && !updateManageButton.confirmed enabled: !updateManageButton.busy - busy: false confirmed: packageData.isRecentlyInstalledChanged button_style: confirmed ? packageData.isInstalled : !packageData.isInstalled @@ -274,7 +270,6 @@ Item visible: (showManageButtons || confirmed) && (packageData.canUpdate || confirmed) && !installManageButton.confirmed enabled: !installManageButton.busy - busy: false confirmed: packageData.isRecentlyUpdatedChanged button_style: true -- cgit v1.2.3 From 4b358496d916f0e99087bf11725bb05a2674abca Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 13:15:16 +0100 Subject: Simplify enable/disable button visibility CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 51e6f83079..55a4ae8731 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && packageData.isInstalled && !(installManageButton.confirmed || updateManageButton.confirmed || packageData.packageType == "material") + visible: showManageButtons && !(installManageButton.confirmed || updateManageButton.confirmed || packageData.packageType == "material") enabled: !(installManageButton.busy || updateManageButton.busy) button_style: !packageData.isActive -- cgit v1.2.3 From 62f99a28b31e1d0fec01151f3413abf36a3b9e09 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 14:15:16 +0100 Subject: Simplify the update logic in the package model / card CURA-8587 --- plugins/Marketplace/PackageModel.py | 57 +++++++++++----------- .../resources/qml/PackageCardHeader.qml | 41 +++------------- 2 files changed, 36 insertions(+), 62 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 0e808dd369..ac2e2df2b2 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -5,7 +5,7 @@ import re from enum import Enum from typing import Any, cast, Dict, List, Optional -from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal +from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal, pyqtSlot from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager @@ -73,16 +73,11 @@ class PackageModel(QObject): self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) - self._is_updating = False self._is_recently_updated = self._getRecentlyUpdated() - - self._is_installing = False - self._is_uninstalling = False self._is_recently_installed = self._getRecentlyInstalled() self.updatePackageTriggered.connect(lambda pkg: self._setIsUpdating(True)) - self.installPackageTriggered.connect(lambda pkg, url: self._setIsInstalling(True)) - self.uninstallPackageTriggered.connect(lambda pkg: self._setIsUninstalling(True)) + self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id, True)) @@ -90,6 +85,8 @@ class PackageModel(QObject): self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id, False)) self._package_manager.packagesWithUpdateChanged.connect(lambda: self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) + self._is_busy = False + def __eq__(self, other: object) -> bool: if isinstance(other, PackageModel): return other == self @@ -308,42 +305,44 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) - installedPackagesChanged = pyqtSignal(bool) + installed = pyqtSignal(bool) + + updated = pyqtSignal(bool) - uninstalledPackagesChanged = pyqtSignal(bool) + busyChanged = pyqtSignal() - updatePackagesChanged = pyqtSignal(bool) + @pyqtSlot() + def install(self): + self.setBusy(True) + self.installPackageTriggered.emit(self.packageId, self.downloadURL) - def _setIsUpdating(self, value: bool) -> None: - self._is_updating = value + @pyqtSlot() + def uninstall(self): + self.setBusy(True) + self.uninstallPackageTriggered.emit(self.packageId) - def _setIsInstalling(self, value: bool) -> None: - self._is_installing = value + @pyqtProperty(bool, notify= busyChanged) + def busy(self): + """ + Property indicating that some kind of upgrade is active. + """ + return self._is_busy - def _setIsUninstalling(self, value: bool) -> None: - self._is_uninstalling = value + def setBusy(self, value: bool): + if self._is_busy != value: + self._is_busy = value + self.busyChanged.emit() def _packageInstalled(self, package_id: str, success: bool) -> None: if self._package_id != package_id: return - if self._is_updating: - self.updatePackagesChanged.emit(success) - self._is_updating = False - if self._is_installing: - self.installedPackagesChanged.emit(success) - self._is_installing = False - if self._is_uninstalling: - self.uninstalledPackagesChanged.emit(success) - self._is_uninstalling = False + self.setBusy(not self._is_busy) + self.stateManageButtonChanged.emit() def _getRecentlyInstalled(self) -> bool: return (self._package_id in self._package_manager.getPackagesToInstall() or self._package_id in self._package_manager.getPackagesToRemove()) \ and self._package_id not in self._package_manager.package_infosWithUpdate - @pyqtProperty(bool, constant = True) - def isRecentlyInstalledChanged(self) -> bool: - return self._is_recently_installed - def _getRecentlyUpdated(self) -> bool: return self._package_id in self._package_manager.package_infosWithUpdate and self._package_id in self._package_manager.getPackagesToInstall() diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 55a4ae8731..96335ecc9d 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -207,18 +207,15 @@ Item id: installManageButton visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled) && !updateManageButton.confirmed enabled: !updateManageButton.busy - - confirmed: packageData.isRecentlyInstalledChanged - - button_style: confirmed ? packageData.isInstalled : !packageData.isInstalled + busy: packageData.busy + button_style: !packageData.isInstalled Layout.alignment: Qt.AlignTop text: { - if (button_style) + if (!packageData.isInstalled) { if (busy) { return catalog.i18nc("@button", "Installing..."); } - else if (confirmed) { return catalog.i18nc("@button", "Installed"); } else { return catalog.i18nc("@button", "Install"); } } else @@ -226,41 +223,19 @@ Item if (packageData.canDowngrade) { if (busy) { return catalog.i18nc("@button", "Downgrading..."); } - else if (confirmed) { return catalog.i18nc("@button", "Downgraded"); } else { return catalog.i18nc("@button", "Downgrade"); } } else { - if (busy) { return catalog.i18nc("@button", "Uninstalling..."); } - else if (confirmed) { return catalog.i18nc("@button", "Uninstalled"); } - else { return catalog.i18nc("@button", "Uninstall"); } + return catalog.i18nc("@button", "Uninstall"); } } } onClicked: { - busy = true - if (packageData.isInstalled){ packageData.installPackageTriggered(packageData.packageId, packageData.downloadURL); } - else { packageData.uninstallPackageTriggered(packageData.packageId); } - } - - Connections - { - target: packageData - - function onInstalledPackagesChanged(success) - { - installManageButton.busy = false; - installManageButton.confirmed = success; - } - function onUninstalledPackagesChanged(success) - { - installManageButton.busy = false; - installManageButton.confirmed = success; - installManageButton.button_style = !installManageButton.button_style; - } - + if (packageData.isInstalled){ packageData.uninstall() } + else { packageData.install()} } } @@ -284,7 +259,7 @@ Item onClicked: { - busy = true; + busy = true packageData.updatePackageTriggered(packageData.packageId); } @@ -292,7 +267,7 @@ Item { target: packageData - function onUpdatePackagesChanged(succes) + function updated(succes) { updateManageButton.busy = false; updateManageButton.confirmed = succes; -- cgit v1.2.3 From d422e0d4eeb014350f4c9ba029f064ed3c21bf72 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 14:18:50 +0100 Subject: Simplify onClicked for install button CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 96335ecc9d..fa45a4d0bb 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -232,11 +232,8 @@ Item } } - onClicked: - { - if (packageData.isInstalled){ packageData.uninstall() } - else { packageData.install()} - } + onClicked: packageData.isInstalled ? packageData.uninstall(): packageData.install() + } ManageButton -- cgit v1.2.3 From d50dc59aacb66ebeb2f82333e40f589898997414 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 15:13:00 +0100 Subject: Simplifications and cleanup CURA-8587 --- plugins/Marketplace/PackageModel.py | 29 +++++++--- plugins/Marketplace/RemotePackageList.py | 2 +- plugins/Marketplace/resources/qml/ManageButton.qml | 2 +- .../resources/qml/PackageCardHeader.qml | 62 +++++----------------- 4 files changed, 37 insertions(+), 58 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index ac2e2df2b2..2d9b013f72 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -6,6 +6,7 @@ from enum import Enum from typing import Any, cast, Dict, List, Optional from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal, pyqtSlot +from PyQt5.QtQml import QQmlEngine from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager @@ -30,6 +31,7 @@ class PackageModel(QObject): :param parent: The parent QML object that controls the lifetime of this model (normally a PackageList). """ super().__init__(parent) + QQmlEngine.setObjectOwnership(self, QQmlEngine.CppOwnership) self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() @@ -78,11 +80,10 @@ class PackageModel(QObject): self.updatePackageTriggered.connect(lambda pkg: self._setIsUpdating(True)) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) - self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id, True)) - self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id, True)) - self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id, False)) + self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) + self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) + self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packagesWithUpdateChanged.connect(lambda: self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) self._is_busy = False @@ -328,16 +329,30 @@ class PackageModel(QObject): """ return self._is_busy + @pyqtSlot() + def enable(self): + self.enablePackageTriggered.emit(self.packageId) + + @pyqtSlot() + def disable(self): + self.disablePackageTriggered.emit(self.packageId) + def setBusy(self, value: bool): if self._is_busy != value: self._is_busy = value - self.busyChanged.emit() + try: + self.busyChanged.emit() + except RuntimeError: + pass - def _packageInstalled(self, package_id: str, success: bool) -> None: + def _packageInstalled(self, package_id: str) -> None: if self._package_id != package_id: return self.setBusy(not self._is_busy) - self.stateManageButtonChanged.emit() + try: + self.stateManageButtonChanged.emit() + except RuntimeError: + pass def _getRecentlyInstalled(self) -> bool: return (self._package_id in self._package_manager.getPackagesToInstall() or self._package_id in self._package_manager.getPackagesToRemove()) \ diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 7cbd00ad76..e877cd9eb5 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -128,7 +128,7 @@ class RemotePackageList(PackageList): # Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling # between de-/constructing RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object # was deleted when it was still parsing the response - return + continue self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self._ongoing_requests["get_packages"] = None diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 87c72d8710..36022ffd54 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 bool button_style + property bool button_style: true property string text property bool busy: false property bool confirmed: false diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index fa45a4d0bb..5a661e32fb 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && !(installManageButton.confirmed || updateManageButton.confirmed || packageData.packageType == "material") + visible: showManageButtons && packageData.packageType != "material" enabled: !(installManageButton.busy || updateManageButton.busy) button_style: !packageData.isActive @@ -189,23 +189,13 @@ Item text: button_style ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") - onClicked: - { - if(packageData.isActive) - { - packageData.disablePackageTriggered(packageData.packageId) - } - else - { - packageData.enablePackageTriggered(packageData.packageId) - } - } + onClicked: packageData.isActive ? packageData.disable(): packageData.enable() } ManageButton { id: installManageButton - visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled) && !updateManageButton.confirmed + visible: showManageButtons && (packageData.canDowngrade || !packageData.isBundled) enabled: !updateManageButton.busy busy: packageData.busy button_style: !packageData.isInstalled @@ -213,6 +203,11 @@ Item text: { + if (packageData.canDowngrade) + { + if (busy) { return catalog.i18nc("@button", "Downgrading..."); } + else { return catalog.i18nc("@button", "Downgrade"); } + } if (!packageData.isInstalled) { if (busy) { return catalog.i18nc("@button", "Installing..."); } @@ -220,56 +215,25 @@ Item } else { - if (packageData.canDowngrade) - { - if (busy) { return catalog.i18nc("@button", "Downgrading..."); } - else { return catalog.i18nc("@button", "Downgrade"); } - } - else - { - return catalog.i18nc("@button", "Uninstall"); - } + return catalog.i18nc("@button", "Uninstall"); } } onClicked: packageData.isInstalled ? packageData.uninstall(): packageData.install() - } ManageButton { id: updateManageButton - visible: (showManageButtons || confirmed) && (packageData.canUpdate || confirmed) && !installManageButton.confirmed + visible: showManageButtons && packageData.canUpdate enabled: !installManageButton.busy - confirmed: packageData.isRecentlyUpdatedChanged - - button_style: true + busy: packageData.busy Layout.alignment: Qt.AlignTop - text: - { - if (busy) { return catalog.i18nc("@button", "Updating..."); } - else if (confirmed) { return catalog.i18nc("@button", "Updated"); } - else { return catalog.i18nc("@button", "Update"); } - } - - onClicked: - { - busy = true - packageData.updatePackageTriggered(packageData.packageId); - } + text: busy ? catalog.i18nc("@button", "Updating..."): catalog.i18nc("@button", "Update") - Connections - { - target: packageData - - function updated(succes) - { - updateManageButton.busy = false; - updateManageButton.confirmed = succes; - } - } + onClicked: packageData.updatePackageTriggered(packageData.packageId); } } } -- cgit v1.2.3 From 08eba9f21a97192513783fd93024d2862885d178 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Dec 2021 15:17:19 +0100 Subject: Fix updating package CURA-8587 --- plugins/Marketplace/PackageModel.py | 16 ---------------- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 2d9b013f72..8b7b800dec 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -75,11 +75,6 @@ class PackageModel(QObject): self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) - self._is_recently_updated = self._getRecentlyUpdated() - self._is_recently_installed = self._getRecentlyInstalled() - - self.updatePackageTriggered.connect(lambda pkg: self._setIsUpdating(True)) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) @@ -354,17 +349,6 @@ class PackageModel(QObject): except RuntimeError: pass - def _getRecentlyInstalled(self) -> bool: - return (self._package_id in self._package_manager.getPackagesToInstall() or self._package_id in self._package_manager.getPackagesToRemove()) \ - and self._package_id not in self._package_manager.package_infosWithUpdate - - def _getRecentlyUpdated(self) -> bool: - return self._package_id in self._package_manager.package_infosWithUpdate and self._package_id in self._package_manager.getPackagesToInstall() - - @pyqtProperty(bool, constant = True) - def isRecentlyUpdatedChanged(self) -> bool: - return self._is_recently_updated - @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: return self._package_id in self._package_manager.local_packages_ids diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 5a661e32fb..9124998ccc 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -233,7 +233,7 @@ Item text: busy ? catalog.i18nc("@button", "Updating..."): catalog.i18nc("@button", "Update") - onClicked: packageData.updatePackageTriggered(packageData.packageId); + onClicked: packageData.install() } } } -- cgit v1.2.3 From c338d8f5ce6f50b48fa35dc8028aff44d3317143 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 14 Dec 2021 16:37:16 +0100 Subject: Only show enable/disable button if plugin is installed CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 9124998ccc..db1661870b 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && packageData.packageType != "material" + visible: showManageButtons && packageData.isInstalled && packageData.packageType != "material" enabled: !(installManageButton.busy || updateManageButton.busy) button_style: !packageData.isActive -- cgit v1.2.3 From 5f444fa5b72738f09c8b244f3a43f571b6c0c660 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 14 Dec 2021 17:49:25 +0100 Subject: Simplify enabled busy state both the `installManageButton` and `updateManageButton` are busy when `packageData.busy`, so the `!(installManageButton.busy || updateManageButton.busy)` check didn't make much sense. CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index db1661870b..ed55d7b362 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -182,7 +182,7 @@ Item { id: enableManageButton visible: showManageButtons && packageData.isInstalled && packageData.packageType != "material" - enabled: !(installManageButton.busy || updateManageButton.busy) + enabled: !packageData.busy button_style: !packageData.isActive Layout.alignment: Qt.AlignTop @@ -196,7 +196,7 @@ Item { id: installManageButton visible: showManageButtons && (packageData.canDowngrade || !packageData.isBundled) - enabled: !updateManageButton.busy + enabled: !packageData.busy busy: packageData.busy button_style: !packageData.isInstalled Layout.alignment: Qt.AlignTop @@ -226,8 +226,7 @@ Item { id: updateManageButton visible: showManageButtons && packageData.canUpdate - enabled: !installManageButton.busy - + enabled: !packageData.busy busy: packageData.busy Layout.alignment: Qt.AlignTop -- cgit v1.2.3 From aea316799a246a3c083a9b8af5c29f8064ce7803 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 15 Dec 2021 10:43:57 +0100 Subject: Always set `busy = False` on installed package CURA-8587 --- plugins/Marketplace/PackageModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 8b7b800dec..02cdcba17e 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -343,7 +343,7 @@ class PackageModel(QObject): def _packageInstalled(self, package_id: str) -> None: if self._package_id != package_id: return - self.setBusy(not self._is_busy) + self.setBusy(False) try: self.stateManageButtonChanged.emit() except RuntimeError: -- cgit v1.2.3 From 62596a42e69bdf9691a9fec4dcc57e2f82f9a843 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 15 Dec 2021 10:44:25 +0100 Subject: Remove un-needed column component CURA-8587 --- plugins/Marketplace/resources/qml/PackageCard.qml | 130 ++++++++++------------ 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 9097417c80..633d2b25b9 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -17,86 +17,78 @@ Rectangle color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width - Column + PackageCardHeader { - width: parent.width + id: packageCardHeader - spacing: 0 - - PackageCardHeader + Item { - id: packageCardHeader - - // description - Item - { - id: shortDescription + id: shortDescription - anchors.fill: parent + anchors.fill: parent - Label + Label + { + id: descriptionLabel + width: parent.width + property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. + + text: packageData.description + textFormat: Text.PlainText //Must be plain text, or we won't get onLineLaidOut signals. Don't auto-detect! + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + visible: text !== "" + + onLineLaidOut: { - id: descriptionLabel - width: parent.width - property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision. - - text: packageData.description - textFormat: Text.PlainText //Must be plain text, or we won't get onLineLaidOut signals. Don't auto-detect! - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - maximumLineCount: 2 - wrapMode: Text.Wrap - elide: Text.ElideRight - visible: text !== "" - - onLineLaidOut: + if(truncated && line.isLast) { - if(truncated && line.isLast) + let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; + if(line.implicitWidth > max_line_width) + { + line.width = max_line_width; + } + else { - let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - 2 * UM.Theme.getSize("default_margin").width; - if(line.implicitWidth > max_line_width) - { - line.width = max_line_width; - } - else - { - line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. - } - descriptionLabel.lastLineWidth = line.implicitWidth; + line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves. } + descriptionLabel.lastLineWidth = line.implicitWidth; } } - Label - { - id: tripleDotLabel - anchors.left: parent.left - anchors.leftMargin: descriptionLabel.lastLineWidth - anchors.bottom: descriptionLabel.bottom - - text: "… " - font: descriptionLabel.font - color: descriptionLabel.color - visible: descriptionLabel.truncated && descriptionLabel.text !== "" - } - Cura.TertiaryButton - { - id: readMoreButton - anchors.right: parent.right - anchors.bottom: descriptionLabel.bottom - height: fontMetrics.height //Height of a single line. - - text: catalog.i18nc("@info", "Read more") - iconSource: UM.Theme.getIcon("LinkExternal") - - visible: descriptionLabel.truncated && descriptionLabel.text !== "" - enabled: visible - leftPadding: UM.Theme.getSize("default_margin").width - rightPadding: UM.Theme.getSize("wide_margin").width - textFont: descriptionLabel.font - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) - } + } + Label + { + id: tripleDotLabel + anchors.left: parent.left + anchors.leftMargin: descriptionLabel.lastLineWidth + anchors.bottom: descriptionLabel.bottom + + text: "… " + font: descriptionLabel.font + color: descriptionLabel.color + visible: descriptionLabel.truncated && descriptionLabel.text !== "" + } + Cura.TertiaryButton + { + id: readMoreButton + anchors.right: parent.right + anchors.bottom: descriptionLabel.bottom + height: fontMetrics.height //Height of a single line. + + text: catalog.i18nc("@info", "Read more") + iconSource: UM.Theme.getIcon("LinkExternal") + + visible: descriptionLabel.truncated && descriptionLabel.text !== "" + enabled: visible + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: UM.Theme.getSize("wide_margin").width + textFont: descriptionLabel.font + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.packageInfoUrl) } } } -- cgit v1.2.3 From 33f1bd8c5d2126e0f3045d9c71514dd23788f9c9 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 15 Dec 2021 14:18:40 +0100 Subject: Simplify `CuraPackageManager` CURA-8587 --- cura/CuraPackageManager.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 535d331a62..313653c8a0 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -19,13 +19,11 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) - self._local_packages: Optional[List[Dict[str, Any]]] = None - self._local_packages_ids: Optional[Set[str]] = None + self._local_packages: Optional[Dict[str, Dict[str, Any]]] = None self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: self._local_packages = self.getAllLocalPackages() - self._local_packages_ids = set(pkg["package_id"] for pkg in self._local_packages) @property def local_packages(self) -> List[Dict[str, Any]]: @@ -34,16 +32,16 @@ class CuraPackageManager(PackageManager): self._updateLocalPackages() # _updateLocalPackages always results in a list of packages, not None. # It's guaranteed to be a list now. - return cast(List[Dict[str, Any]], self._local_packages) + return list(self._local_packages.values()) @property def local_packages_ids(self) -> Set[str]: """locally installed packages, lazy execution""" - if self._local_packages_ids is None: + if self._local_packages is None: self._updateLocalPackages() # _updateLocalPackages always results in a list of packages, not None. # It's guaranteed to be a list now. - return cast(Set[str], self._local_packages_ids) + return set(self._local_packages.keys()) def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) @@ -75,17 +73,11 @@ class CuraPackageManager(PackageManager): return machine_with_materials, machine_with_qualities - def getAllLocalPackages(self) -> List[Dict[str, Any]]: + def getAllLocalPackages(self) -> Dict[str, Dict[str, Any]]: """ returns an unordered list of all the package_info installed, to be installed or to be returned""" + packages = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) + packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) + packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) - class PkgInfo(dict): - # Needed helper class because a dict isn't hashable - def __eq__(self, item): - return item == self["package_id"] - - packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] - packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToRemove().values() if package["package_info"]["package_id"] not in packages]) - packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToInstall().values() if package["package_info"]["package_id"] not in packages]) - - return [dict(package) for package in packages] + return packages -- cgit v1.2.3 From 8b2ced122ca746c3b5c9effb4db73e43b8f9c9c5 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 15 Dec 2021 15:07:23 +0100 Subject: Explicitly close the downloaded curapackage Sometime very small curapackage where download and the `canInstallChanged` signal was emitted before the zipfile was completely processed. This could result in a failed installation, which was often the case for materials. I also narrowed down the try-catch block. --- plugins/Marketplace/PackageList.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 390bf841df..57d8c22183 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -209,24 +209,26 @@ class PackageList(ListModel): ) def _downloadFinished(self, package_id: str, reply: "QNetworkReply", update: bool = False) -> None: - try: - with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file: + with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file: + try: bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) while bytes_read: temp_file.write(bytes_read) bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) - self._to_install[package_id] = temp_file.name - self._ongoing_requests["download_package"] = None - self.canInstallChanged.emit(package_id, update) - except IOError as e: - Logger.error(f"Failed to write downloaded package to temp file {e}") - temp_file.close() - self._downloadError(package_id, update) - 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 - # was deleted when it was still parsing the response - return + except IOError as e: + Logger.error(f"Failed to write downloaded package to temp file {e}") + temp_file.close() + self._downloadError(package_id, update) + 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 + # was deleted when it was still parsing the response + temp_file.close() + return + temp_file.close() + self._to_install[package_id] = temp_file.name + self._ongoing_requests["download_package"] = None + self.canInstallChanged.emit(package_id, update) def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if reply: -- cgit v1.2.3 From c66e17dd9e52987ff75b708557e9037f4d9d66f2 Mon Sep 17 00:00:00 2001 From: casper Date: Wed, 15 Dec 2021 15:13:38 +0100 Subject: Show install button after package has been uninstalled CURA-8587 --- cura/CuraPackageManager.py | 32 ++++++++++++++++++++++---------- plugins/Marketplace/PackageModel.py | 2 +- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 313653c8a0..2a016bdf6e 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -19,11 +19,23 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) + self._local_packages: Optional[Dict[str, Dict[str, Any]]] = None + self._local_packages_installed: Optional[Dict[str, Dict[str, Any]]] = None + self._local_packages_to_remove: Optional[Dict[str, Dict[str, Any]]] = None + self._local_packages_to_install: Optional[Dict[str, Dict[str, Any]]] = None + self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: - self._local_packages = self.getAllLocalPackages() + self._local_packages_installed = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) + self._local_packages_to_remove = dict([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) + self._local_packages_to_install = dict([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) + + self._local_packages = {} + self._local_packages.update(self._local_packages_installed) + self._local_packages.update(self._local_packages_to_remove) + self._local_packages.update(self._local_packages_to_install) @property def local_packages(self) -> List[Dict[str, Any]]: @@ -43,6 +55,15 @@ class CuraPackageManager(PackageManager): # It's guaranteed to be a list now. return set(self._local_packages.keys()) + @property + def installed_packages_ids(self) -> Set[str]: + """locally installed packages, lazy execution""" + if self._local_packages is None: + self._updateLocalPackages() + # _updateLocalPackages always results in a list of packages, not None. + # It's guaranteed to be a list now. + return set(self._local_packages_installed.keys()) + def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) @@ -72,12 +93,3 @@ class CuraPackageManager(PackageManager): machine_with_qualities.append((global_stack, str(extruder_nr), container_id)) return machine_with_materials, machine_with_qualities - - def getAllLocalPackages(self) -> Dict[str, Dict[str, Any]]: - """ returns an unordered list of all the package_info installed, to be installed or to be returned""" - - packages = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) - packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) - packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) - - return packages diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 02cdcba17e..f88b1416b5 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -351,7 +351,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in self._package_manager.local_packages_ids + return self._package_id in self._package_manager.installed_packages_ids @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self) -> bool: -- cgit v1.2.3 From 9e1e98bdbd053d70d50f796c4204498f59dd0c1c Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 15 Dec 2021 16:38:54 +0100 Subject: Revert "Show install button after package has been uninstalled" This reverts commit c66e17dd9e52987ff75b708557e9037f4d9d66f2. --- cura/CuraPackageManager.py | 32 ++++++++++---------------------- plugins/Marketplace/PackageModel.py | 2 +- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 2a016bdf6e..313653c8a0 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -19,23 +19,11 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) - self._local_packages: Optional[Dict[str, Dict[str, Any]]] = None - self._local_packages_installed: Optional[Dict[str, Dict[str, Any]]] = None - self._local_packages_to_remove: Optional[Dict[str, Dict[str, Any]]] = None - self._local_packages_to_install: Optional[Dict[str, Dict[str, Any]]] = None - self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: - self._local_packages_installed = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) - self._local_packages_to_remove = dict([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) - self._local_packages_to_install = dict([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) - - self._local_packages = {} - self._local_packages.update(self._local_packages_installed) - self._local_packages.update(self._local_packages_to_remove) - self._local_packages.update(self._local_packages_to_install) + self._local_packages = self.getAllLocalPackages() @property def local_packages(self) -> List[Dict[str, Any]]: @@ -55,15 +43,6 @@ class CuraPackageManager(PackageManager): # It's guaranteed to be a list now. return set(self._local_packages.keys()) - @property - def installed_packages_ids(self) -> Set[str]: - """locally installed packages, lazy execution""" - if self._local_packages is None: - self._updateLocalPackages() - # _updateLocalPackages always results in a list of packages, not None. - # It's guaranteed to be a list now. - return set(self._local_packages_installed.keys()) - def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) @@ -93,3 +72,12 @@ class CuraPackageManager(PackageManager): machine_with_qualities.append((global_stack, str(extruder_nr), container_id)) return machine_with_materials, machine_with_qualities + + def getAllLocalPackages(self) -> Dict[str, Dict[str, Any]]: + """ returns an unordered list of all the package_info installed, to be installed or to be returned""" + + packages = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) + packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) + packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) + + return packages diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index f88b1416b5..02cdcba17e 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -351,7 +351,7 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in self._package_manager.installed_packages_ids + return self._package_id in self._package_manager.local_packages_ids @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self) -> bool: -- cgit v1.2.3 From 0ffaafc8c0159769f797f38f68241d56ce803288 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 15 Dec 2021 16:39:02 +0100 Subject: Revert "Simplify `CuraPackageManager`" This reverts commit 33f1bd8c5d2126e0f3045d9c71514dd23788f9c9. --- cura/CuraPackageManager.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 313653c8a0..535d331a62 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -19,11 +19,13 @@ if TYPE_CHECKING: class CuraPackageManager(PackageManager): def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) - self._local_packages: Optional[Dict[str, Dict[str, Any]]] = None + self._local_packages: Optional[List[Dict[str, Any]]] = None + self._local_packages_ids: Optional[Set[str]] = None self.installedPackagesChanged.connect(self._updateLocalPackages) def _updateLocalPackages(self) -> None: self._local_packages = self.getAllLocalPackages() + self._local_packages_ids = set(pkg["package_id"] for pkg in self._local_packages) @property def local_packages(self) -> List[Dict[str, Any]]: @@ -32,16 +34,16 @@ class CuraPackageManager(PackageManager): self._updateLocalPackages() # _updateLocalPackages always results in a list of packages, not None. # It's guaranteed to be a list now. - return list(self._local_packages.values()) + return cast(List[Dict[str, Any]], self._local_packages) @property def local_packages_ids(self) -> Set[str]: """locally installed packages, lazy execution""" - if self._local_packages is None: + if self._local_packages_ids is None: self._updateLocalPackages() # _updateLocalPackages always results in a list of packages, not None. # It's guaranteed to be a list now. - return set(self._local_packages.keys()) + return cast(Set[str], self._local_packages_ids) def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) @@ -73,11 +75,17 @@ class CuraPackageManager(PackageManager): return machine_with_materials, machine_with_qualities - def getAllLocalPackages(self) -> Dict[str, Dict[str, Any]]: + def getAllLocalPackages(self) -> List[Dict[str, Any]]: """ returns an unordered list of all the package_info installed, to be installed or to be returned""" - packages = dict([(package_info["package_id"], dict(package_info)) for package in self.getAllInstalledPackagesInfo().values() for package_info in package]) - packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToRemove().values()]) - packages.update([(package["package_info"]["package_id"], dict(package["package_info"])) for package in self.getPackagesToInstall().values()]) - return packages + class PkgInfo(dict): + # Needed helper class because a dict isn't hashable + def __eq__(self, item): + return item == self["package_id"] + + packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] + packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToRemove().values() if package["package_info"]["package_id"] not in packages]) + packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToInstall().values() if package["package_info"]["package_id"] not in packages]) + + return [dict(package) for package in packages] -- cgit v1.2.3 From 23cc7084c48ec346ef32e2a5b35f264e44648d28 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 15 Dec 2021 17:51:38 +0100 Subject: Switch between correct states of the un-/installed buttons Contributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 5 ++++- plugins/Marketplace/PackageModel.py | 7 +++++-- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 57d8c22183..2c4137394b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -19,7 +19,7 @@ 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 -from .Constants import USER_PACKAGES_URL +from .Constants import USER_PACKAGES_URL, PACKAGES_URL if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -195,6 +195,9 @@ class PackageList(ListModel): :param update: A flag if this is download request is an update process """ + if url == "": + url = f"{PACKAGES_URL}/{package_id}/download" + def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 02cdcba17e..213d5bf616 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -314,7 +314,6 @@ class PackageModel(QObject): @pyqtSlot() def uninstall(self): - self.setBusy(True) self.uninstallPackageTriggered.emit(self.packageId) @pyqtProperty(bool, notify= busyChanged) @@ -351,7 +350,11 @@ class PackageModel(QObject): @pyqtProperty(bool, notify = stateManageButtonChanged) def isInstalled(self) -> bool: - return self._package_id in self._package_manager.local_packages_ids + return self._package_id in self._package_manager.getAllInstalledPackageIDs() + + @pyqtProperty(bool, notify = stateManageButtonChanged) + def isToBeInstalled(self) -> bool: + return self._package_id in self._package_manager.getPackagesToInstall() @pyqtProperty(bool, notify = stateManageButtonChanged) def isActive(self) -> bool: diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index ed55d7b362..41c0bd6a95 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -198,7 +198,7 @@ Item visible: showManageButtons && (packageData.canDowngrade || !packageData.isBundled) enabled: !packageData.busy busy: packageData.busy - button_style: !packageData.isInstalled + button_style: packageData.isInstalled || packageData.isToBeInstalled Layout.alignment: Qt.AlignTop text: @@ -208,7 +208,7 @@ Item if (busy) { return catalog.i18nc("@button", "Downgrading..."); } else { return catalog.i18nc("@button", "Downgrade"); } } - if (!packageData.isInstalled) + if (!(packageData.isInstalled || packageData.isToBeInstalled)) { if (busy) { return catalog.i18nc("@button", "Installing..."); } else { return catalog.i18nc("@button", "Install"); } @@ -219,7 +219,7 @@ Item } } - onClicked: packageData.isInstalled ? packageData.uninstall(): packageData.install() + onClicked: packageData.isInstalled || packageData.isToBeInstalled ? packageData.uninstall(): packageData.install() } ManageButton -- cgit v1.2.3 From 951c0234d6c02e7140e31bbb6dd1c8fda9f03062 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 10:06:54 +0100 Subject: Renamed _manager to _package_manager for more consistent naming Contributes to CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 10 +++++----- plugins/Marketplace/PackageList.py | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 49a08296f5..616f6d95bf 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -40,7 +40,7 @@ class LocalPackageList(PackageList): super().__init__(parent) self._has_footer = False self._ongoing_requests["check_updates"] = None - self._manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate) + self._package_manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate) def _sortSectionsOnUpdate(self) -> None: SECTION_ORDER = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) @@ -56,9 +56,9 @@ class LocalPackageList(PackageList): self.setIsLoading(True) # Obtain and sort the local packages - self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.local_packages]]) + self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._package_manager.local_packages]]) self._sortSectionsOnUpdate() - self.checkForUpdates(self._manager.local_packages) + self.checkForUpdates(self._package_manager.local_packages) self.setIsLoading(False) self.setHasMore(False) # All packages should have been loaded at this time @@ -67,7 +67,7 @@ class LocalPackageList(PackageList): """ Create a PackageModel from the package_info and determine its section_title""" package_id = package_info["package_id"] - bundled_or_installed = "bundled" if self._manager.isBundledPackage(package_id) else "installed" + bundled_or_installed = "bundled" if self._package_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) @@ -99,5 +99,5 @@ class LocalPackageList(PackageList): return packages = response_data["data"] - self._manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) + self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) self._ongoing_requests["check_updates"] = None diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 2c4137394b..af23a2a6fe 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -37,7 +37,7 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() self._account = CuraApplication.getInstance().getCuraAPI().account self._error_message = "" @@ -47,7 +47,7 @@ class PackageList(ListModel): self._has_footer = True self._to_install: Dict[str, str] = {} self.canInstallChanged.connect(self._requestInstall) - self._local_packages: Set[str] = {p["package_id"] for p in self._manager.local_packages} + self._local_packages: Set[str] = {p["package_id"] for p in self._package_manager.local_packages} self._ongoing_requests: Dict[str, Optional[HttpRequestData]] = {"download_package": None} self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) @@ -165,11 +165,11 @@ class PackageList(ListModel): if dialog is not None: dialog.deleteLater() # reset package card - self._manager.packageInstallingFailed.emit(package_id) + self._package_manager.packageInstallingFailed.emit(package_id) def _requestInstall(self, package_id: str, update: bool = False) -> None: package_path = self._to_install[package_id] - license_content = self._manager.getPackageLicense(package_path) + license_content = self._package_manager.getPackageLicense(package_path) if not update and license_content is not None: # If installation is not and update, and the packages contains a license then @@ -181,8 +181,9 @@ class PackageList(ListModel): def _install(self, package_id: str, update: bool = False) -> None: package_path = self._to_install.pop(package_id) - to_be_installed = self._manager.installPackage(package_path) is not None + to_be_installed = self._package_manager.installPackage(package_path) is not None if not to_be_installed: + Logger.warning(f"Could not install {package_id}") return package = self.getPackageModel(package_id) self.subscribeUserToPackage(package_id, str(package.sdk_version)) @@ -237,7 +238,7 @@ class PackageList(ListModel): if reply: reply_string = bytes(reply.readAll()).decode() Logger.error(f"Failed to download package: {package_id} due to {reply_string}") - self._manager.packageInstallingFailed.emit(package_id) + self._package_manager.packageInstallingFailed.emit(package_id) def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: """Subscribe the user (if logged in) to the package for a given SDK @@ -279,7 +280,7 @@ class PackageList(ListModel): :param package_id: the package identification string """ - self._manager.removePackage(package_id) + self._package_manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) def updatePackage(self, package_id: str) -> None: @@ -287,6 +288,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ - self._manager.removePackage(package_id, force_add = not self._manager.isBundledPackage(package_id)) - url = self._manager.package_infosWithUpdate[package_id]["download_url"] + self._package_manager.removePackage(package_id, force_add = not self._package_manager.isBundledPackage(package_id)) + url = self._package_manager.package_infosWithUpdate[package_id]["download_url"] self.download(package_id, url, True) -- cgit v1.2.3 From 447e0443a289a7d8a6dd99393ca06a102fd83591 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 10:08:24 +0100 Subject: Reinstall a package scheduled for removal before attempting to dl and install Contributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index af23a2a6fe..741532499d 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -273,7 +273,8 @@ class PackageList(ListModel): :param package_id: the package identification string """ - self.download(package_id, url, False) + if not self._package_manager.reinstallPackage(package_id): + self.download(package_id, url, False) def uninstallPackage(self, package_id: str) -> None: """Uninstall a package from the Marketplace -- cgit v1.2.3 From 020313da25770950eb061689b1d83e5e8ad24a71 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 10:23:23 +0100 Subject: Subscribe the user to a reinstalled package again Contributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 741532499d..2fd62156f0 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -275,6 +275,9 @@ class PackageList(ListModel): """ if not self._package_manager.reinstallPackage(package_id): self.download(package_id, url, False) + else: + package = self.getPackageModel(package_id) + self.subscribeUserToPackage(package_id, str(package.sdk_version)) def uninstallPackage(self, package_id: str) -> None: """Uninstall a package from the Marketplace -- cgit v1.2.3 From aa93186707488bf1df59c94904f0e7019cad6f59 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 10:34:08 +0100 Subject: Don't show License Dialog when there is no License text Contributes to CURA-8587 --- 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 2fd62156f0..4ba312ea6c 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -171,7 +171,7 @@ class PackageList(ListModel): package_path = self._to_install[package_id] license_content = self._package_manager.getPackageLicense(package_path) - if not update and license_content is not None: + if not update and license_content is not None and license_content != "": # If installation is not and update, and the packages contains a license then # open dialog, prompting the using to accept the plugin license self._openLicenseDialog(package_id, license_content) -- cgit v1.2.3 From 14406e13bdee59c393249003d081e50a63fbbda1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Dec 2021 10:42:44 +0100 Subject: Rename hasPluginsEnabledOrDisabledChanged to pluginsEnabledOrDisabledChanged This makes it more in line with the other signal naming CURA-8587 --- plugins/Marketplace/PackageModel.py | 2 +- plugins/Marketplace/RestartManager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 213d5bf616..1a3ebcee44 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -75,7 +75,7 @@ class PackageModel(QObject): self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin) self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) + self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged) self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id)) diff --git a/plugins/Marketplace/RestartManager.py b/plugins/Marketplace/RestartManager.py index 19650dd64e..9fe52b4116 100644 --- a/plugins/Marketplace/RestartManager.py +++ b/plugins/Marketplace/RestartManager.py @@ -18,7 +18,7 @@ class RestartManager(QObject): self._plugin_registry: "PluginRegistry" = CuraApplication.getInstance().getPluginRegistry() self._manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) - self._plugin_registry.hasPluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) + self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) self._restart_needed = False -- cgit v1.2.3 From 6703813f1de360f992b605d1cadeb1531dbf0a78 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Dec 2021 10:48:36 +0100 Subject: Remove unused signals CURA-8587 --- plugins/Marketplace/PackageModel.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 1a3ebcee44..dc62682aba 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -301,10 +301,6 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) - installed = pyqtSignal(bool) - - updated = pyqtSignal(bool) - busyChanged = pyqtSignal() @pyqtSlot() -- cgit v1.2.3 From 62c6af1ef30219ed1d1fc717c9b821969ef7025b Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 11:31:51 +0100 Subject: Don't show the enable button on recently installed plugins Contributes to CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 41c0bd6a95..921a57870b 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,7 +181,7 @@ Item ManageButton { id: enableManageButton - visible: showManageButtons && packageData.isInstalled && packageData.packageType != "material" + visible: showManageButtons && packageData.isInstalled && !packageData.isToBeInstalled && packageData.packageType != "material" enabled: !packageData.busy button_style: !packageData.isActive -- cgit v1.2.3 From 8abeb24cccae9850985eece83422737033eade0d Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 12:21:36 +0100 Subject: Remove a recently installed and then uninstalled package from the manage list Contributes to CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 10 ++++++++++ plugins/Marketplace/PackageList.py | 1 - plugins/Marketplace/RemotePackageList.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 616f6d95bf..3e6127ad54 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -41,11 +41,21 @@ class LocalPackageList(PackageList): self._has_footer = False self._ongoing_requests["check_updates"] = None self._package_manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate) + self._package_manager.packageUninstalled.connect(self._removePackageModel) def _sortSectionsOnUpdate(self) -> None: SECTION_ORDER = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) self.sort(lambda model: f"{SECTION_ORDER[model.sectionTitle]}_{model._can_update}_{model.displayName}".lower(), key = "package") + def _removePackageModel(self, package_id): + if package_id not in self._package_manager.local_packages_ids: + index = self.find("package", package_id) + if index < 0: + Logger.error(f"Could not find card in Listview corresponding with {package_id}") + self.updatePackages() + return + self.removeItem(index) + @pyqtSlot() def updatePackages(self) -> None: """Update the list with local packages, these are materials or plugin, either bundled or user installed. The list diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 4ba312ea6c..133f42ebb0 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -47,7 +47,6 @@ class PackageList(ListModel): self._has_footer = True self._to_install: Dict[str, str] = {} self.canInstallChanged.connect(self._requestInstall) - self._local_packages: Set[str] = {p["package_id"] for p in self._package_manager.local_packages} self._ongoing_requests: Dict[str, Optional[HttpRequestData]] = {"download_package": None} self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index e877cd9eb5..16b0e721ad 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -118,7 +118,7 @@ class RemotePackageList(PackageList): for package_data in response_data["data"]: package_id = package_data["package_id"] - if package_id in self._local_packages: + if package_id in self._package_manager.local_packages_ids: continue # We should only show packages which are not already installed try: package = PackageModel(package_data, parent = self) -- cgit v1.2.3 From 23d6c2390f80239bf66a03cbe0df54e72f53c303 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 12:37:41 +0100 Subject: Fixed the updating button Contributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 5 +++-- plugins/Marketplace/PackageModel.py | 7 ++++++- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 133f42ebb0..7eb98b3233 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -286,11 +286,12 @@ class PackageList(ListModel): self._package_manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) - def updatePackage(self, package_id: str) -> None: + def updatePackage(self, package_id: str, url: str) -> None: """Update a package from the Marketplace :param package_id: the package identification string """ self._package_manager.removePackage(package_id, force_add = not self._package_manager.isBundledPackage(package_id)) - url = self._package_manager.package_infosWithUpdate[package_id]["download_url"] + if url == "": + url = self._package_manager.package_infosWithUpdate[package_id]["download_url"] self.download(package_id, url, True) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index dc62682aba..917a54ede6 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -295,7 +295,7 @@ class PackageModel(QObject): uninstallPackageTriggered = pyqtSignal(str) - updatePackageTriggered = pyqtSignal(str) + updatePackageTriggered = pyqtSignal(str, str) enablePackageTriggered = pyqtSignal(str) @@ -308,6 +308,11 @@ class PackageModel(QObject): self.setBusy(True) self.installPackageTriggered.emit(self.packageId, self.downloadURL) + @pyqtSlot() + def update(self): + self.setBusy(True) + self.updatePackageTriggered.emit(self.packageId, self.downloadURL) + @pyqtSlot() def uninstall(self): self.uninstallPackageTriggered.emit(self.packageId) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 921a57870b..3d99b23907 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -232,7 +232,7 @@ Item text: busy ? catalog.i18nc("@button", "Updating..."): catalog.i18nc("@button", "Update") - onClicked: packageData.install() + onClicked: packageData.update() } } } -- cgit v1.2.3 From ffa34ab5feaf5266612bbbdbed1eb513393850d5 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 17:00:29 +0100 Subject: Make sure Signal are disconnected when PackageModel is deleted Contributes to CURA-8587 --- plugins/Marketplace/PackageList.py | 2 ++ plugins/Marketplace/PackageModel.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 7eb98b3233..80588a0c12 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -55,6 +55,8 @@ class PackageList(ListModel): def __del__(self) -> None: """ When this object is deleted it will loop through all registered API requests and aborts them """ self.cleanUpAPIRequest() + self.isLoadingChanged.disconnect() + self.hasMoreChanged.disconnect() def abortRequest(self, request_id: str) -> None: """Aborts a single request""" diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 917a54ede6..334b54e6f5 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -79,10 +79,17 @@ class PackageModel(QObject): self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id)) self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id)) - self._package_manager.packagesWithUpdateChanged.connect(lambda: self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate)) + self._package_manager.packagesWithUpdateChanged.connect(self._processUpdatedPackages) self._is_busy = False + @pyqtSlot() + def _processUpdatedPackages(self): + self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate) + + def __del__(self): + self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages) + def __eq__(self, other: object) -> bool: if isinstance(other, PackageModel): return other == self -- cgit v1.2.3 From 0a7aee5c09a215d6f4ee0b90ec96f6b8e46451d4 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 17:03:14 +0100 Subject: Only remove Card from List when package is deleted Updated packages should still be present in the list Contribute to CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 3e6127ad54..3d4ee8cc1b 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -48,7 +48,8 @@ class LocalPackageList(PackageList): self.sort(lambda model: f"{SECTION_ORDER[model.sectionTitle]}_{model._can_update}_{model.displayName}".lower(), key = "package") def _removePackageModel(self, package_id): - if package_id not in self._package_manager.local_packages_ids: + package = self.getPackageModel(package_id) + if not package.canUpdate and package_id in self._package_manager.getPackagesToRemove() and package_id not in self._package_manager.getPackagesToInstall(): index = self.find("package", package_id) if index < 0: Logger.error(f"Could not find card in Listview corresponding with {package_id}") -- cgit v1.2.3 From 4d8592c6b77de24a372b720f6c9bf52d9530d3a8 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 17:07:42 +0100 Subject: Catch runtime errors when trying to disconnect signal Contribute to CURA-8587 --- plugins/Marketplace/PackageList.py | 7 +++++-- plugins/Marketplace/PackageModel.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 80588a0c12..2c1da39182 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -55,8 +55,11 @@ class PackageList(ListModel): def __del__(self) -> None: """ When this object is deleted it will loop through all registered API requests and aborts them """ self.cleanUpAPIRequest() - self.isLoadingChanged.disconnect() - self.hasMoreChanged.disconnect() + try: + self.isLoadingChanged.disconnect() + self.hasMoreChanged.disconnect() + except RuntimeError: + pass def abortRequest(self, request_id: str) -> None: """Aborts a single request""" diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 334b54e6f5..52edd60d1b 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -88,7 +88,10 @@ class PackageModel(QObject): self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate) def __del__(self): - self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages) + try: + self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages) + except RuntimeError: + pass def __eq__(self, other: object) -> bool: if isinstance(other, PackageModel): -- cgit v1.2.3 From bd7a73e7ef6122abd71d553064231e9587c4466c Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 16 Dec 2021 17:31:08 +0100 Subject: Invert style of install button Contribute to CURA-8587 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 3d99b23907..feebe4d8b5 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -198,7 +198,7 @@ Item visible: showManageButtons && (packageData.canDowngrade || !packageData.isBundled) enabled: !packageData.busy busy: packageData.busy - button_style: packageData.isInstalled || packageData.isToBeInstalled + button_style: !(packageData.isInstalled || packageData.isToBeInstalled) Layout.alignment: Qt.AlignTop text: -- cgit v1.2.3 From fa7ad7ddb19e44363548eeba02b04b9611f83bb9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Dec 2021 22:54:12 +0100 Subject: Simplify the packageList CURA-8587 --- plugins/Marketplace/PackageList.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 2c1da39182..e559d6b43e 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -297,6 +297,4 @@ class PackageList(ListModel): :param package_id: the package identification string """ self._package_manager.removePackage(package_id, force_add = not self._package_manager.isBundledPackage(package_id)) - if url == "": - url = self._package_manager.package_infosWithUpdate[package_id]["download_url"] self.download(package_id, url, True) -- cgit v1.2.3 From 477f62916c8aec86d71ac309621d72c8a32d0889 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Dec 2021 23:21:44 +0100 Subject: Further simplify the data being sent over to the package manager CURA-8587 --- cura/CuraPackageManager.py | 3 --- plugins/Marketplace/LocalPackageList.py | 6 ++++-- plugins/Toolbox/src/Toolbox.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 535d331a62..79763351b9 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -85,7 +85,4 @@ class CuraPackageManager(PackageManager): return item == self["package_id"] packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] - packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToRemove().values() if package["package_info"]["package_id"] not in packages]) - packages.extend([PkgInfo(package["package_info"]) for package in self.getPackagesToInstall().values() if package["package_info"]["package_id"] not in packages]) - return [dict(package) for package in packages] diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 3d4ee8cc1b..b531c4040f 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -49,7 +49,7 @@ class LocalPackageList(PackageList): def _removePackageModel(self, package_id): package = self.getPackageModel(package_id) - if not package.canUpdate and package_id in self._package_manager.getPackagesToRemove() and package_id not in self._package_manager.getPackagesToInstall(): + if not package.canUpdate and package_id in self._package_manager.getToRemovePackageIDs() and package_id not in self._package_manager.getPackagesToInstall(): index = self.find("package", package_id) if index < 0: Logger.error(f"Could not find card in Listview corresponding with {package_id}") @@ -110,5 +110,7 @@ class LocalPackageList(PackageList): return packages = response_data["data"] - self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) + + self._package_manager.setPackagesWithUpdate({p['package_id'] for p in packages}) + self._ongoing_requests["check_updates"] = None diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 5644bace7a..20eec3352b 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -635,7 +635,7 @@ class Toolbox(QObject, Extension): elif request_type == "updates": # Tell the package manager that there's a new set of updates available. packages = self._server_response_data[request_type] - self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages]))) + self._package_manager.setPackagesWithUpdate({p['package_id'] for p in packages}) self.metadataChanged.emit() -- cgit v1.2.3 From 0cfd576c8f4fa5799c64515a47f623c6814ae441 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Dec 2021 23:35:38 +0100 Subject: Fix setting available version numbers CURA-8587 --- plugins/Toolbox/src/Toolbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 20eec3352b..e300d0ff34 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -645,7 +645,7 @@ class Toolbox(QObject, Extension): # This function goes through all known remote versions of a package and notifies the package manager of this change def _notifyPackageManager(self): for package in self._server_response_data["packages"]: - self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]), package) + self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"])) def _onDownloadFinished(self, reply: "QNetworkReply") -> None: self.resetDownload() -- cgit v1.2.3 From 4b4229e20a47685576fe76212c27593558228d28 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 00:44:56 +0100 Subject: Fix downgrading bundled packages CURA-8587 --- plugins/Marketplace/PackageModel.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 52edd60d1b..c67a971934 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -85,7 +85,7 @@ class PackageModel(QObject): @pyqtSlot() def _processUpdatedPackages(self): - self.setCanUpdate(self._package_id in self._package_manager.packagesWithUpdate) + self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id)) def __del__(self): try: @@ -377,9 +377,8 @@ class PackageModel(QObject): return self._package_manager.canDowngrade(self._package_id) def setCanUpdate(self, value: bool) -> None: - if value != self._can_update: - self._can_update = value - self.stateManageButtonChanged.emit() + self._can_update = value + self.stateManageButtonChanged.emit() @pyqtProperty(bool, fset = setCanUpdate, notify = stateManageButtonChanged) def canUpdate(self) -> bool: -- cgit v1.2.3 From d015d94965cc8c5ad2cc2165e4362e8d456f9492 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 16:23:54 +0100 Subject: Update plugins/Marketplace/LocalPackageList.py CURA-8587 Co-authored-by: Ghostkeeper --- plugins/Marketplace/LocalPackageList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index b531c4040f..95db67a58f 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -86,7 +86,7 @@ class LocalPackageList(PackageList): return package def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None: - installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) + installed_packages = "&".join([f"installed_packages={package['package_id']}:{package['package_version']}" for package in packages]) request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" self._ongoing_requests["check_updates"] = HttpRequestManager.getInstance().get( -- cgit v1.2.3 From 8a198f79112ee6c14887b9128c6ff9633d6a97c7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 16:31:11 +0100 Subject: Simplify sorting CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 95db67a58f..48d06d7b0b 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -44,8 +44,8 @@ class LocalPackageList(PackageList): self._package_manager.packageUninstalled.connect(self._removePackageModel) def _sortSectionsOnUpdate(self) -> None: - SECTION_ORDER = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) - self.sort(lambda model: f"{SECTION_ORDER[model.sectionTitle]}_{model._can_update}_{model.displayName}".lower(), key = "package") + section_order = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) + self.sort(lambda model: (section_order[model.sectionTitle], model.canUpdate, model.displayName.lower()), key = "package") def _removePackageModel(self, package_id): package = self.getPackageModel(package_id) -- cgit v1.2.3 From 83c78c4b4db9e9da3a0532fe26898a1da4af4bf3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 16:41:52 +0100 Subject: Remove canInstallChanged signal It's not needed, this can just be handled with a direct call! CURA-8587 --- 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 e559d6b43e..73393aa9da 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -46,7 +46,6 @@ class PackageList(ListModel): self._has_more = False self._has_footer = True self._to_install: Dict[str, str] = {} - self.canInstallChanged.connect(self._requestInstall) self._ongoing_requests: Dict[str, Optional[HttpRequestData]] = {"download_package": None} self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) @@ -135,8 +134,6 @@ class PackageList(ListModel): index = self.find("package", package_id) return self.getItem(index)["package"] - canInstallChanged = pyqtSignal(str, bool) - def _openLicenseDialog(self, package_id: str, license_content: str) -> None: plugin_path = self._plugin_registry.getPluginPath("Marketplace") if plugin_path is None: @@ -236,7 +233,8 @@ class PackageList(ListModel): temp_file.close() self._to_install[package_id] = temp_file.name self._ongoing_requests["download_package"] = None - self.canInstallChanged.emit(package_id, update) + self._requestInstall(package_id, update) + def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if reply: -- cgit v1.2.3 From a2a76fefddc0254480f28f7a6953b26f686e1be9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 16:48:06 +0100 Subject: Move cleaning up of request to after signals are disconnected CURA-8587 --- plugins/Marketplace/PackageList.py | 4 +++- plugins/Marketplace/PackageModel.py | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 73393aa9da..1a76d65141 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -53,13 +53,15 @@ class PackageList(ListModel): def __del__(self) -> None: """ When this object is deleted it will loop through all registered API requests and aborts them """ - self.cleanUpAPIRequest() + try: self.isLoadingChanged.disconnect() self.hasMoreChanged.disconnect() except RuntimeError: pass + self.cleanUpAPIRequest() + def abortRequest(self, request_id: str) -> None: """Aborts a single request""" if request_id in self._ongoing_requests and self._ongoing_requests[request_id]: diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index c67a971934..307cdce986 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -88,10 +88,7 @@ class PackageModel(QObject): self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id)) def __del__(self): - try: - self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages) - except RuntimeError: - pass + self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages) def __eq__(self, other: object) -> bool: if isinstance(other, PackageModel): -- cgit v1.2.3 From 8df204b327e8503b2de6978e7b220105d6016462 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Dec 2021 16:49:16 +0100 Subject: Remove unneeded control CURA-8587 --- .../resources/qml/PackageCardHeader.qml | 26 ---------------------- 1 file changed, 26 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index feebe4d8b5..0bf93fc67c 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -69,32 +69,6 @@ Item visible: packageData.isCheckedByUltimaker } - Control - { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - Layout.alignment: Qt.AlignCenter - enabled: false // remove! - visible: false // replace packageInfo.XXXXXX - // TODO: waiting for materials card implementation - - Cura.ToolTip - { - tooltipText: "" // TODO - visible: parent.hovered - } - - UM.RecolorImage - { - anchors.fill: parent - - color: UM.Theme.getColor("primary") - source: UM.Theme.getIcon("CheckCircle") // TODO - } - - // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO - } - Label { id: packageVersionLabel -- cgit v1.2.3 From afef4f761bbda772ba62b85af8b718669aef8aee Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Dec 2021 10:17:30 +0100 Subject: Update documentation CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 48d06d7b0b..8adb1e841e 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -35,7 +35,6 @@ class LocalPackageList(PackageList): } } # The section headers to be used for the different package categories - def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._has_footer = False @@ -47,9 +46,15 @@ class LocalPackageList(PackageList): section_order = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"])) self.sort(lambda model: (section_order[model.sectionTitle], model.canUpdate, model.displayName.lower()), key = "package") - def _removePackageModel(self, package_id): + def _removePackageModel(self, package_id: str) -> None: + """ + Cleanup function to remove the package model from the list. Note that this is only done if the package can't + be updated, it is in the to remove list and isn't in the to be installed list + """ package = self.getPackageModel(package_id) - if not package.canUpdate and package_id in self._package_manager.getToRemovePackageIDs() and package_id not in self._package_manager.getPackagesToInstall(): + if not package.canUpdate and \ + package_id in self._package_manager.getToRemovePackageIDs() and \ + package_id not in self._package_manager.getPackagesToInstall(): index = self.find("package", package_id) if index < 0: Logger.error(f"Could not find card in Listview corresponding with {package_id}") -- cgit v1.2.3 From 6dac500f187470ba8aa3e9f42faaa1b2fcb57a0a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Dec 2021 10:30:53 +0100 Subject: Simplify getAllLocalPackages CURA-8587 --- cura/CuraPackageManager.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 79763351b9..af75aa7b66 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -76,13 +76,10 @@ class CuraPackageManager(PackageManager): return machine_with_materials, machine_with_qualities def getAllLocalPackages(self) -> List[Dict[str, Any]]: - """ returns an unordered list of all the package_info installed, to be installed or to be returned""" + """ Returns an unordered list of all the package_info of installed, to be installed, or bundled packages""" + packages: List[Dict[str, Any]] = [] + for packages_to_add in self.getAllInstalledPackagesInfo().values(): + packages.extend(packages_to_add) - class PkgInfo(dict): - # Needed helper class because a dict isn't hashable - def __eq__(self, item): - return item == self["package_id"] - - packages = [PkgInfo(package_info) for package in self.getAllInstalledPackagesInfo().values() for package_info in package] - return [dict(package) for package in packages] + return packages -- cgit v1.2.3 From 82598681605c0210deebeee57f4df1ed448607da Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 22 Dec 2021 10:04:27 +0100 Subject: Fix update URL CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 8adb1e841e..8de153660c 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -92,7 +92,7 @@ class LocalPackageList(PackageList): def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None: installed_packages = "&".join([f"installed_packages={package['package_id']}:{package['package_version']}" for package in packages]) - request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" + request_url = f"{PACKAGE_UPDATES_URL}?{installed_packages}" self._ongoing_requests["check_updates"] = HttpRequestManager.getInstance().get( request_url, -- cgit v1.2.3 From 532c7a2109930e30cf794445e249f966a34fc963 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 22 Dec 2021 10:19:33 +0100 Subject: Let new marketplace also check for updates CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 8de153660c..aca520c937 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -6,6 +6,8 @@ from operator import attrgetter from PyQt5.QtCore import pyqtSlot, QObject +from UM.Version import Version + if TYPE_CHECKING: from PyQt5.QtCore import QObject from PyQt5.QtNetwork import QNetworkReply @@ -115,7 +117,7 @@ class LocalPackageList(PackageList): return packages = response_data["data"] - - self._package_manager.setPackagesWithUpdate({p['package_id'] for p in packages}) + for package in packages: + self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"])) self._ongoing_requests["check_updates"] = None -- cgit v1.2.3 From cf7772a40a64c0049fe80bd9d936d825715c5aa4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 22 Dec 2021 14:27:24 +0100 Subject: Ensure that local list has update URL CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 4 ++++ plugins/Marketplace/PackageList.py | 1 - plugins/Marketplace/PackageModel.py | 9 ++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index aca520c937..ab28e634c2 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -119,5 +119,9 @@ class LocalPackageList(PackageList): packages = response_data["data"] for package in packages: self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"])) + package_model = self.getPackageModel(package["package_id"]) + if package_model: + # Also make sure that the local list knows where to get an update + package_model.setDownloadUrl(package["download_url"]) self._ongoing_requests["check_updates"] = None diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 1a76d65141..ddc39e0c94 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -237,7 +237,6 @@ class PackageList(ListModel): self._ongoing_requests["download_package"] = None self._requestInstall(package_id, update) - def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if reply: reply_string = bytes(reply.readAll()).decode() diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 307cdce986..7c2a5d9ae1 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -290,9 +290,8 @@ class PackageModel(QObject): def isBundled(self) -> bool: return self._is_bundled - @pyqtProperty(str, constant = True) - def downloadURL(self) -> str: - return self._download_url + def setDownloadUrl(self, download_url): + self._download_url = download_url # --- manage buttons signals --- @@ -313,12 +312,12 @@ class PackageModel(QObject): @pyqtSlot() def install(self): self.setBusy(True) - self.installPackageTriggered.emit(self.packageId, self.downloadURL) + self.installPackageTriggered.emit(self.packageId, self._download_url) @pyqtSlot() def update(self): self.setBusy(True) - self.updatePackageTriggered.emit(self.packageId, self.downloadURL) + self.updatePackageTriggered.emit(self.packageId, self._download_url) @pyqtSlot() def uninstall(self): -- cgit v1.2.3 From b794ad6ed22889d5b9c7857132b53a31b32b77b6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 30 Dec 2021 11:51:54 +0100 Subject: Delete old 'Toolbox' in favour of new Marketplace. part of CURA-8588 --- cura/CuraApplication.py | 2 +- plugins/Toolbox/__init__.py | 15 - plugins/Toolbox/plugin.json | 7 - plugins/Toolbox/resources/images/Shop.svg | 6 - .../Toolbox/resources/images/installed_check.svg | 8 - plugins/Toolbox/resources/images/placeholder.svg | 3 - plugins/Toolbox/resources/qml/Toolbox.qml | 112 --- .../qml/components/ToolboxActionButtonStyle.qml | 29 - .../resources/qml/components/ToolboxBackColumn.qml | 84 -- .../qml/components/ToolboxCompatibilityChart.qml | 209 ----- .../resources/qml/components/ToolboxDetailList.qml | 43 - .../resources/qml/components/ToolboxDetailTile.qml | 74 -- .../qml/components/ToolboxDetailTileActions.qml | 121 --- .../qml/components/ToolboxDownloadsGrid.qml | 45 -- .../qml/components/ToolboxDownloadsGridTile.qml | 125 --- .../qml/components/ToolboxDownloadsShowcase.qml | 84 -- .../components/ToolboxDownloadsShowcaseTile.qml | 114 --- .../resources/qml/components/ToolboxFooter.qml | 60 -- .../resources/qml/components/ToolboxHeader.qml | 112 --- .../qml/components/ToolboxInstalledTile.qml | 123 --- .../qml/components/ToolboxInstalledTileActions.qml | 90 --- .../qml/components/ToolboxProgressButton.qml | 60 -- .../resources/qml/components/ToolboxShadow.qml | 24 - .../resources/qml/components/ToolboxTabButton.qml | 68 -- .../resources/qml/dialogs/CompatibilityDialog.qml | 153 ---- .../dialogs/ToolboxConfirmUninstallResetDialog.qml | 96 --- .../resources/qml/dialogs/ToolboxLicenseDialog.qml | 110 --- .../resources/qml/pages/ToolboxAuthorPage.qml | 176 ----- .../resources/qml/pages/ToolboxDetailPage.qml | 188 ----- .../resources/qml/pages/ToolboxDownloadsPage.qml | 46 -- .../resources/qml/pages/ToolboxErrorPage.qml | 23 - .../resources/qml/pages/ToolboxInstalledPage.qml | 223 ------ .../resources/qml/pages/ToolboxLoadingPage.qml | 25 - .../Toolbox/resources/qml/pages/WelcomePage.qml | 44 -- plugins/Toolbox/src/AuthorsModel.py | 104 --- plugins/Toolbox/src/CloudApiModel.py | 29 - plugins/Toolbox/src/CloudSync/CloudApiClient.py | 52 -- .../Toolbox/src/CloudSync/CloudPackageChecker.py | 164 ---- .../src/CloudSync/DiscrepanciesPresenter.py | 41 - plugins/Toolbox/src/CloudSync/DownloadPresenter.py | 153 ---- plugins/Toolbox/src/CloudSync/LicenseModel.py | 77 -- plugins/Toolbox/src/CloudSync/LicensePresenter.py | 142 ---- .../src/CloudSync/RestartApplicationPresenter.py | 32 - .../src/CloudSync/SubscribedPackagesModel.py | 74 -- plugins/Toolbox/src/CloudSync/SyncOrchestrator.py | 114 --- plugins/Toolbox/src/CloudSync/__init__.py | 0 plugins/Toolbox/src/ConfigsModel.py | 38 - plugins/Toolbox/src/PackagesModel.py | 161 ---- plugins/Toolbox/src/Toolbox.py | 878 --------------------- plugins/Toolbox/src/__init__.py | 0 resources/bundled_packages/cura.json | 8 +- resources/qml/MainWindow/ApplicationMenu.qml | 10 +- resources/qml/MainWindow/MainWindowHeader.qml | 47 -- resources/qml/Widgets/ScrollView.qml | 2 +- scripts/check_invalid_imports.py | 2 +- 55 files changed, 8 insertions(+), 4822 deletions(-) delete mode 100644 plugins/Toolbox/__init__.py delete mode 100644 plugins/Toolbox/plugin.json delete mode 100755 plugins/Toolbox/resources/images/Shop.svg delete mode 100644 plugins/Toolbox/resources/images/installed_check.svg delete mode 100644 plugins/Toolbox/resources/images/placeholder.svg delete mode 100644 plugins/Toolbox/resources/qml/Toolbox.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxActionButtonStyle.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxBackColumn.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxCompatibilityChart.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDetailList.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDetailTile.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDetailTileActions.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDownloadsGrid.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDownloadsGridTile.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcase.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcaseTile.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxFooter.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxHeader.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxInstalledTile.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxInstalledTileActions.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxProgressButton.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxShadow.qml delete mode 100644 plugins/Toolbox/resources/qml/components/ToolboxTabButton.qml delete mode 100644 plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml delete mode 100644 plugins/Toolbox/resources/qml/dialogs/ToolboxConfirmUninstallResetDialog.qml delete mode 100644 plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxAuthorPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxDetailPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxDownloadsPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxErrorPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxInstalledPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/ToolboxLoadingPage.qml delete mode 100644 plugins/Toolbox/resources/qml/pages/WelcomePage.qml delete mode 100644 plugins/Toolbox/src/AuthorsModel.py delete mode 100644 plugins/Toolbox/src/CloudApiModel.py delete mode 100644 plugins/Toolbox/src/CloudSync/CloudApiClient.py delete mode 100644 plugins/Toolbox/src/CloudSync/CloudPackageChecker.py delete mode 100644 plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py delete mode 100644 plugins/Toolbox/src/CloudSync/DownloadPresenter.py delete mode 100644 plugins/Toolbox/src/CloudSync/LicenseModel.py delete mode 100644 plugins/Toolbox/src/CloudSync/LicensePresenter.py delete mode 100644 plugins/Toolbox/src/CloudSync/RestartApplicationPresenter.py delete mode 100644 plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py delete mode 100644 plugins/Toolbox/src/CloudSync/SyncOrchestrator.py delete mode 100644 plugins/Toolbox/src/CloudSync/__init__.py delete mode 100644 plugins/Toolbox/src/ConfigsModel.py delete mode 100644 plugins/Toolbox/src/PackagesModel.py delete mode 100644 plugins/Toolbox/src/Toolbox.py delete mode 100644 plugins/Toolbox/src/__init__.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 21924a2680..b312d8afd2 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -493,7 +493,7 @@ class CuraApplication(QtApplication): "CuraEngineBackend", #Cura is useless without this one since you can't slice. "FileLogger", #You want to be able to read the log if something goes wrong. "XmlMaterialProfile", #Cura crashes without this one. - "Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back. + "Marketplace", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back. "PrepareStage", #Cura is useless without this one since you can't load models. "PreviewStage", #This shows the list of the plugin views that are installed in Cura. "MonitorStage", #Major part of Cura's functionality. diff --git a/plugins/Toolbox/__init__.py b/plugins/Toolbox/__init__.py deleted file mode 100644 index 51f1b643d0..0000000000 --- a/plugins/Toolbox/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Toolbox is released under the terms of the LGPLv3 or higher. - -from .src import Toolbox -from .src.CloudSync.SyncOrchestrator import SyncOrchestrator - - -def getMetaData(): - return {} - - -def register(app): - return { - "extension": [Toolbox.Toolbox(app), SyncOrchestrator(app)] - } diff --git a/plugins/Toolbox/plugin.json b/plugins/Toolbox/plugin.json deleted file mode 100644 index ed4a3eae97..0000000000 --- a/plugins/Toolbox/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Toolbox", - "author": "Ultimaker B.V.", - "version": "1.0.1", - "api": 7, - "description": "Find, manage and install new Cura packages." -} diff --git a/plugins/Toolbox/resources/images/Shop.svg b/plugins/Toolbox/resources/images/Shop.svg deleted file mode 100755 index 5056a25c51..0000000000 --- a/plugins/Toolbox/resources/images/Shop.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/plugins/Toolbox/resources/images/installed_check.svg b/plugins/Toolbox/resources/images/installed_check.svg deleted file mode 100644 index 1f1302770b..0000000000 --- a/plugins/Toolbox/resources/images/installed_check.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/plugins/Toolbox/resources/images/placeholder.svg b/plugins/Toolbox/resources/images/placeholder.svg deleted file mode 100644 index cc674a4b38..0000000000 --- a/plugins/Toolbox/resources/images/placeholder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/plugins/Toolbox/resources/qml/Toolbox.qml b/plugins/Toolbox/resources/qml/Toolbox.qml deleted file mode 100644 index b67d175194..0000000000 --- a/plugins/Toolbox/resources/qml/Toolbox.qml +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -// Main window for the Toolbox - -import QtQuick 2.2 -import QtQuick.Dialogs 1.1 -import QtQuick.Window 2.2 -import UM 1.1 as UM - -import "./pages" -import "./dialogs" -import "./components" - -Window -{ - id: base - property var selection: null - title: catalog.i18nc("@title", "Marketplace") - modality: Qt.ApplicationModal - flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint - - width: UM.Theme.getSize("large_popup_dialog").width - height: UM.Theme.getSize("large_popup_dialog").height - minimumWidth: width - maximumWidth: minimumWidth - minimumHeight: height - maximumHeight: minimumHeight - color: UM.Theme.getColor("main_background") - UM.I18nCatalog - { - id: catalog - name: "cura" - } - Item - { - anchors.fill: parent - - WelcomePage - { - visible: toolbox.viewPage === "welcome" - } - - ToolboxHeader - { - id: header - visible: toolbox.viewPage !== "welcome" - } - - Item - { - id: mainView - width: parent.width - z: parent.z - 1 - anchors - { - top: header.bottom - bottom: footer.top - } - // TODO: This could be improved using viewFilter instead of viewCategory - ToolboxLoadingPage - { - id: viewLoading - visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "loading" - } - ToolboxErrorPage - { - id: viewErrored - visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "errored" - } - ToolboxDownloadsPage - { - id: viewDownloads - visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "overview" - } - ToolboxDetailPage - { - id: viewDetail - visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "detail" - } - ToolboxAuthorPage - { - id: viewAuthor - visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "author" - } - ToolboxInstalledPage - { - id: installedPluginList - visible: toolbox.viewCategory === "installed" - } - } - - ToolboxFooter - { - id: footer - visible: toolbox.restartRequired - height: visible ? UM.Theme.getSize("toolbox_footer").height : 0 - } - - Connections - { - target: toolbox - function onShowLicenseDialog() { licenseDialog.show() } - function onCloseLicenseDialog() { licenseDialog.close() } - } - - ToolboxLicenseDialog - { - id: licenseDialog - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxActionButtonStyle.qml b/plugins/Toolbox/resources/qml/components/ToolboxActionButtonStyle.qml deleted file mode 100644 index eff74278c9..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxActionButtonStyle.qml +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM - -ButtonStyle -{ - background: Rectangle - { - implicitWidth: UM.Theme.getSize("toolbox_action_button").width - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - color: "transparent" - border - { - width: UM.Theme.getSize("default_lining").width - color: UM.Theme.getColor("lining") - } - } - label: Label - { - text: control.text - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } -} \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/components/ToolboxBackColumn.qml b/plugins/Toolbox/resources/qml/components/ToolboxBackColumn.qml deleted file mode 100644 index 9874a977f5..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxBackColumn.qml +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM - -Item -{ - id: sidebar - height: parent.height - width: UM.Theme.getSize("toolbox_back_column").width - anchors - { - top: parent.top - left: parent.left - topMargin: UM.Theme.getSize("wide_margin").height - leftMargin: UM.Theme.getSize("default_margin").width - rightMargin: UM.Theme.getSize("default_margin").width - } - Button - { - id: button - text: catalog.i18nc("@action:button", "Back") - enabled: !toolbox.isDownloading - UM.RecolorImage - { - id: backArrow - anchors - { - verticalCenter: parent.verticalCenter - left: parent.left - rightMargin: UM.Theme.getSize("default_margin").width - } - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize - { - width: width - height: height - } - color: button.enabled ? (button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive") - source: UM.Theme.getIcon("ChevronSingleLeft") - } - width: UM.Theme.getSize("toolbox_back_button").width - height: UM.Theme.getSize("toolbox_back_button").height - onClicked: - { - toolbox.viewPage = "overview" - if (toolbox.viewCategory == "material") - { - toolbox.filterModelByProp("authors", "package_types", "material") - } - else if (toolbox.viewCategory == "plugin") - { - toolbox.filterModelByProp("packages", "type", "plugin") - } - - } - style: ButtonStyle - { - background: Rectangle - { - color: "transparent" - } - label: Label - { - id: labelStyle - text: control.text - color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive") - font: UM.Theme.getFont("medium_bold") - horizontalAlignment: Text.AlignLeft - anchors - { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - } - width: control.width - renderType: Text.NativeRendering - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxCompatibilityChart.qml b/plugins/Toolbox/resources/qml/components/ToolboxCompatibilityChart.qml deleted file mode 100644 index e1f88a473f..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxCompatibilityChart.qml +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 - -import UM 1.5 as UM - -Item -{ - id: base - - property var packageData - property var technicalDataSheetUrl: packageData.links.technicalDataSheet - property var safetyDataSheetUrl: packageData.links.safetyDataSheet - property var printingGuidelinesUrl: packageData.links.printingGuidelines - property var materialWebsiteUrl: packageData.links.website - - height: childrenRect.height - onVisibleChanged: packageData.type === "material" && (compatibilityItem.visible || dataSheetLinks.visible) - - Column - { - id: compatibilityItem - visible: packageData.has_configs - width: parent.width - // This is a bit of a hack, but the whole QML is pretty messy right now. This needs a big overhaul. - height: visible ? heading.height + table.height: 0 - - Label - { - id: heading - width: parent.width - text: catalog.i18nc("@label", "Compatibility") - wrapMode: Text.WordWrap - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - - TableView - { - id: table - width: parent.width - frameVisible: false - - // Workaround for scroll issues (QTBUG-49652) - flickableItem.interactive: false - Component.onCompleted: - { - for (var i = 0; i < flickableItem.children.length; ++i) - { - flickableItem.children[i].enabled = false - } - } - selectionMode: 0 - model: packageData.supported_configs - headerDelegate: Rectangle - { - color: UM.Theme.getColor("main_background") - height: UM.Theme.getSize("toolbox_chart_row").height - Label - { - anchors.verticalCenter: parent.verticalCenter - elide: Text.ElideRight - text: styleData.value || "" - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default_bold") - renderType: Text.NativeRendering - } - Rectangle - { - anchors.bottom: parent.bottom - height: UM.Theme.getSize("default_lining").height - width: parent.width - color: "black" - } - } - rowDelegate: Item - { - height: UM.Theme.getSize("toolbox_chart_row").height - Label - { - anchors.verticalCenter: parent.verticalCenter - elide: Text.ElideRight - text: styleData.value || "" - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - } - } - itemDelegate: Item - { - height: UM.Theme.getSize("toolbox_chart_row").height - Label - { - anchors.verticalCenter: parent.verticalCenter - elide: Text.ElideRight - text: styleData.value || "" - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - } - } - - Component - { - id: columnTextDelegate - Label - { - anchors.fill: parent - verticalAlignment: Text.AlignVCenter - text: styleData.value || "" - elide: Text.ElideRight - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - } - } - - TableViewColumn - { - role: "machine" - title: catalog.i18nc("@label:table_header", "Machine") - width: Math.floor(table.width * 0.25) - delegate: columnTextDelegate - } - TableViewColumn - { - role: "print_core" - title: "Print Core" //This term should not be translated. - width: Math.floor(table.width * 0.2) - } - TableViewColumn - { - role: "build_plate" - title: catalog.i18nc("@label:table_header", "Build Plate") - width: Math.floor(table.width * 0.225) - } - TableViewColumn - { - role: "support_material" - title: catalog.i18nc("@label:table_header", "Support") - width: Math.floor(table.width * 0.225) - } - TableViewColumn - { - role: "quality" - title: catalog.i18nc("@label:table_header", "Quality") - width: Math.floor(table.width * 0.1) - } - } - } - - Label - { - id: dataSheetLinks - anchors.top: compatibilityItem.bottom - anchors.topMargin: UM.Theme.getSize("narrow_margin").height - visible: base.technicalDataSheetUrl !== undefined || - base.safetyDataSheetUrl !== undefined || - base.printingGuidelinesUrl !== undefined || - base.materialWebsiteUrl !== undefined - - text: - { - var result = "" - if (base.technicalDataSheetUrl !== undefined) - { - var tds_name = catalog.i18nc("@action:label", "Technical Data Sheet") - result += "%2".arg(base.technicalDataSheetUrl).arg(tds_name) - } - if (base.safetyDataSheetUrl !== undefined) - { - if (result.length > 0) - { - result += "
" - } - var sds_name = catalog.i18nc("@action:label", "Safety Data Sheet") - result += "%2".arg(base.safetyDataSheetUrl).arg(sds_name) - } - if (base.printingGuidelinesUrl !== undefined) - { - if (result.length > 0) - { - result += "
" - } - var pg_name = catalog.i18nc("@action:label", "Printing Guidelines") - result += "%2".arg(base.printingGuidelinesUrl).arg(pg_name) - } - if (base.materialWebsiteUrl !== undefined) - { - if (result.length > 0) - { - result += "
" - } - var pg_name = catalog.i18nc("@action:label", "Website") - result += "%2".arg(base.materialWebsiteUrl).arg(pg_name) - } - - return result - } - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) - renderType: Text.NativeRendering - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDetailList.qml b/plugins/Toolbox/resources/qml/components/ToolboxDetailList.qml deleted file mode 100644 index 22c6b6045f..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDetailList.qml +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 -import UM 1.1 as UM - -Item -{ - id: detailList - ScrollView - { - clip: true - anchors.fill: detailList - - Column - { - anchors - { - right: parent.right - topMargin: UM.Theme.getSize("wide_margin").height - bottomMargin: UM.Theme.getSize("wide_margin").height - top: parent.top - } - height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height - spacing: UM.Theme.getSize("default_margin").height - - Repeater - { - model: toolbox.packagesModel - delegate: Loader - { - // FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete, - // leaving an empty space below the title part. We turn it off for now to make it work on Mac and - // Windows. - // Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992 - asynchronous: false - source: "ToolboxDetailTile.qml" - } - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/components/ToolboxDetailTile.qml deleted file mode 100644 index 5badc6b66d..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDetailTile.qml +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 - -import UM 1.1 as UM - -Item -{ - id: tile - width: detailList.width - UM.Theme.getSize("wide_margin").width - height: normalData.height + 2 * UM.Theme.getSize("wide_margin").height - Column - { - id: normalData - - anchors - { - top: parent.top - left: parent.left - right: controls.left - rightMargin: UM.Theme.getSize("wide_margin").width - } - - Label - { - width: parent.width - height: UM.Theme.getSize("toolbox_property_label").height - text: model.name - wrapMode: Text.WordWrap - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("medium_bold") - renderType: Text.NativeRendering - } - - Label - { - width: parent.width - text: model.description - maximumLineCount: 25 - elide: Text.ElideRight - wrapMode: Text.WordWrap - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - } - - ToolboxCompatibilityChart - { - width: parent.width - packageData: model - } - } - - ToolboxDetailTileActions - { - id: controls - anchors.right: tile.right - anchors.top: tile.top - width: childrenRect.width - height: childrenRect.height - packageData: model - } - - Rectangle - { - color: UM.Theme.getColor("lining") - width: tile.width - height: UM.Theme.getSize("default_lining").height - anchors.top: normalData.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + UM.Theme.getSize("wide_margin").height //Normal margin for spacing after chart, wide margin between items. - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDetailTileActions.qml b/plugins/Toolbox/resources/qml/components/ToolboxDetailTileActions.qml deleted file mode 100644 index d683877605..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDetailTileActions.qml +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.5 as UM -import Cura 1.1 as Cura - -Column -{ - property bool installed: toolbox.isInstalled(model.id) - property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1 - property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn - property var packageData - - width: UM.Theme.getSize("toolbox_action_button").width - spacing: UM.Theme.getSize("narrow_margin").height - - Item - { - width: installButton.width - height: installButton.height - ToolboxProgressButton - { - id: installButton - active: toolbox.isDownloading && toolbox.activePackage == model - onReadyAction: - { - toolbox.activePackage = model - toolbox.startDownload(model.download_url) - } - onActiveAction: toolbox.cancelDownload() - - // Don't allow installing while another download is running - enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired) - opacity: enabled ? 1.0 : 0.5 - visible: !updateButton.visible && !installed // Don't show when the update button is visible - } - - Cura.SecondaryButton - { - id: installedButton - visible: installed - onClicked: toolbox.viewCategory = "installed" - text: catalog.i18nc("@action:button", "Installed") - fixedWidthMode: true - width: installButton.width - height: installButton.height - } - } - - Label - { - wrapMode: Text.WordWrap - text: catalog.i18nc("@label:The string between and is the highlighted link", "Log in is required to install or update") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - visible: loginRequired - width: installButton.width - renderType: Text.NativeRendering - - MouseArea - { - anchors.fill: parent - onClicked: Cura.API.account.login() - } - } - - Label - { - property var whereToBuyUrl: - { - var pg_name = "whereToBuy" - return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined - } - - renderType: Text.NativeRendering - text: catalog.i18nc("@label:The string between and is the highlighted link", "Buy material spools") - linkColor: UM.Theme.getColor("text_link") - visible: whereToBuyUrl != undefined - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - MouseArea - { - anchors.fill: parent - onClicked: UM.UrlUtil.openUrl(parent.whereToBuyUrl, ["https", "http"]) - } - } - - ToolboxProgressButton - { - id: updateButton - active: toolbox.isDownloading && toolbox.activePackage == model - readyLabel: catalog.i18nc("@action:button", "Update") - activeLabel: catalog.i18nc("@action:button", "Updating") - completeLabel: catalog.i18nc("@action:button", "Updated") - - onReadyAction: - { - toolbox.activePackage = model - toolbox.update(model.id) - } - onActiveAction: toolbox.cancelDownload() - // Don't allow installing while another download is running - enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired - opacity: enabled ? 1.0 : 0.5 - visible: canUpdate - } - - Connections - { - target: toolbox - function onInstallChanged() { installed = toolbox.isInstalled(model.id) } - function onFilterChanged() - { - installed = toolbox.isInstalled(model.id) - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGrid.qml b/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGrid.qml deleted file mode 100644 index 6682281a31..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGrid.qml +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 -import UM 1.1 as UM - -Column -{ - property var heading: "" - property var model - id: gridArea - height: childrenRect.height + 2 * padding - width: parent.width - spacing: UM.Theme.getSize("default_margin").height - padding: UM.Theme.getSize("wide_margin").height - Label - { - id: heading - text: gridArea.heading - width: parent.width - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("large") - renderType: Text.NativeRendering - } - Grid - { - id: grid - width: parent.width - 2 * parent.padding - columns: 2 - columnSpacing: UM.Theme.getSize("default_margin").height - rowSpacing: UM.Theme.getSize("default_margin").width - Repeater - { - model: gridArea.model - delegate: Loader - { - asynchronous: true - width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns) - height: UM.Theme.getSize("toolbox_thumbnail_small").height - source: "ToolboxDownloadsGridTile.qml" - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGridTile.qml b/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGridTile.qml deleted file mode 100644 index c310bd7121..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsGridTile.qml +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Layouts 1.3 -import UM 1.1 as UM -import Cura 1.1 as Cura - -Item -{ - id: toolboxDownloadsGridTile - property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1 - property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0) - height: childrenRect.height - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - - MouseArea - { - anchors.fill: parent - hoverEnabled: true - onEntered: thumbnail.border.color = UM.Theme.getColor("primary") - onExited: thumbnail.border.color = UM.Theme.getColor("lining") - onClicked: - { - base.selection = model - switch(toolbox.viewCategory) - { - case "material": - - // If model has a type, it must be a package - if (model.type !== undefined) - { - toolbox.viewPage = "detail" - toolbox.filterModelByProp("packages", "id", model.id) - } - else - { - toolbox.viewPage = "author" - toolbox.setFilters("packages", { - "author_id": model.id, - "type": "material" - }) - } - break - default: - toolbox.viewPage = "detail" - toolbox.filterModelByProp("packages", "id", model.id) - break - } - } - } - - Rectangle - { - id: thumbnail - width: UM.Theme.getSize("toolbox_thumbnail_small").width - height: UM.Theme.getSize("toolbox_thumbnail_small").height - color: UM.Theme.getColor("main_background") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - - Image - { - anchors.centerIn: parent - width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width - height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width - sourceSize.width: width - sourceSize.height: height - fillMode: Image.PreserveAspectFit - source: model.icon_url || "../../images/placeholder.svg" - mipmap: true - } - UM.RecolorImage - { - width: (parent.width * 0.4) | 0 - height: (parent.height * 0.4) | 0 - anchors - { - bottom: parent.bottom - right: parent.right - } - sourceSize.height: height - visible: installedPackages != 0 - color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border") - source: "../../images/installed_check.svg" - } - } - Item - { - anchors - { - left: thumbnail.right - leftMargin: Math.floor(UM.Theme.getSize("narrow_margin").width) - right: parent.right - top: parent.top - bottom: parent.bottom - } - - Label - { - id: name - text: model.name - width: parent.width - elide: Text.ElideRight - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default_bold") - } - Label - { - id: info - text: model.description - elide: Text.ElideRight - width: parent.width - wrapMode: Text.WordWrap - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default") - anchors.top: name.bottom - anchors.bottom: parent.bottom - verticalAlignment: Text.AlignVCenter - maximumLineCount: 2 - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcase.qml b/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcase.qml deleted file mode 100644 index a42a10aa29..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcase.qml +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM - -Rectangle -{ - color: UM.Theme.getColor("toolbox_premium_packages_background") - height: childrenRect.height - width: parent.width - Column - { - height: childrenRect.height + 2 * padding - spacing: UM.Theme.getSize("default_margin").height - width: parent.width - padding: UM.Theme.getSize("wide_margin").height - Item - { - width: parent.width - parent.padding * 2 - height: childrenRect.height - Label - { - id: heading - text: catalog.i18nc("@label", "Premium") - width: contentWidth - height: contentHeight - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("large") - renderType: Text.NativeRendering - } - UM.TooltipArea - { - width: childrenRect.width - height: childrenRect.height - anchors.right: parent.right - text: catalog.i18nc("@info:tooltip", "Go to Web Marketplace") - Label - { - text: "".arg(toolbox.getWebMarketplaceUrl("materials") + "?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search") + catalog.i18nc("@label", "Search materials") + "" - width: contentWidth - height: contentHeight - horizontalAlignment: Text.AlignRight - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - - linkColor: UM.Theme.getColor("text_link") - onLinkActivated: Qt.openUrlExternally(link) - - visible: toolbox.viewCategory === "material" - } - } - } - Grid - { - height: childrenRect.height - spacing: UM.Theme.getSize("wide_margin").width - columns: 3 - anchors.horizontalCenter: parent.horizontalCenter - - Repeater - { - model: - { - if (toolbox.viewCategory == "plugin") - { - return toolbox.pluginsShowcaseModel - } - if (toolbox.viewCategory == "material") - { - return toolbox.materialsShowcaseModel - } - } - delegate: Loader - { - asynchronous: true - source: "ToolboxDownloadsShowcaseTile.qml" - } - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcaseTile.qml b/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcaseTile.qml deleted file mode 100644 index 6695921126..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxDownloadsShowcaseTile.qml +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 - -import UM 1.1 as UM - -Rectangle -{ - property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1 - property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0) - id: tileBase - width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width) - height: thumbnail.height + packageName.height + UM.Theme.getSize("default_margin").width - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - color: UM.Theme.getColor("main_background") - Image - { - id: thumbnail - height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height - width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height - sourceSize.height: height - sourceSize.width: width - fillMode: Image.PreserveAspectFit - source: model.icon_url || "../../images/placeholder.svg" - mipmap: true - anchors - { - top: parent.top - topMargin: UM.Theme.getSize("default_margin").height - horizontalCenter: parent.horizontalCenter - } - } - Label - { - id: packageName - text: model.name - anchors - { - horizontalCenter: parent.horizontalCenter - top: thumbnail.bottom - } - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - renderType: Text.NativeRendering - height: UM.Theme.getSize("toolbox_heading_label").height - width: parent.width - UM.Theme.getSize("default_margin").width - wrapMode: Text.WordWrap - elide: Text.ElideRight - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - } - UM.RecolorImage - { - width: (parent.width * 0.20) | 0 - height: width - anchors - { - bottom: bottomBorder.top - right: parent.right - } - visible: installedPackages != 0 - color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border") - source: "../../images/installed_check.svg" - } - - Rectangle - { - id: bottomBorder - color: UM.Theme.getColor("primary") - anchors.bottom: parent.bottom - width: parent.width - height: UM.Theme.getSize("toolbox_header_highlight").height - } - - MouseArea - { - anchors.fill: parent - hoverEnabled: true - onEntered: tileBase.border.color = UM.Theme.getColor("primary") - onExited: tileBase.border.color = UM.Theme.getColor("lining") - onClicked: - { - base.selection = model - switch(toolbox.viewCategory) - { - case "material": - - // If model has a type, it must be a package - if (model.type !== undefined) - { - toolbox.viewPage = "detail" - toolbox.filterModelByProp("packages", "id", model.id) - } - else - { - toolbox.viewPage = "author" - toolbox.setFilters("packages", { - "author_id": model.id, - "type": "material" - }) - } - break - default: - toolbox.viewPage = "detail" - toolbox.filterModelByProp("packages", "id", model.id) - break - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml b/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml deleted file mode 100644 index 9863bd9a93..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 - -import UM 1.1 as UM -import Cura 1.0 as Cura - -Item -{ - id: footer - width: parent.width - anchors.bottom: parent.bottom - height: visible ? UM.Theme.getSize("toolbox_footer").height : 0 - - Label - { - text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.") - color: UM.Theme.getColor("text") - height: UM.Theme.getSize("toolbox_footer_button").height - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - anchors - { - top: restartButton.top - left: parent.left - leftMargin: UM.Theme.getSize("wide_margin").width - right: restartButton.left - rightMargin: UM.Theme.getSize("default_margin").width - } - renderType: Text.NativeRendering - } - - Cura.PrimaryButton - { - id: restartButton - anchors - { - top: parent.top - topMargin: UM.Theme.getSize("default_margin").height - right: parent.right - rightMargin: UM.Theme.getSize("wide_margin").width - } - height: UM.Theme.getSize("toolbox_footer_button").height - text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) - onClicked: - { - base.hide() - toolbox.restart() - } - } - - ToolboxShadow - { - visible: footer.visible - anchors.bottom: footer.top - reversed: true - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxHeader.qml b/plugins/Toolbox/resources/qml/components/ToolboxHeader.qml deleted file mode 100644 index 2c43110af9..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxHeader.qml +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2020 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 - -import UM 1.4 as UM -import Cura 1.0 as Cura - -Item -{ - id: header - width: parent.width - height: UM.Theme.getSize("toolbox_header").height - Row - { - id: bar - spacing: UM.Theme.getSize("default_margin").width - height: childrenRect.height - width: childrenRect.width - anchors - { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - } - - ToolboxTabButton - { - id: pluginsTabButton - text: catalog.i18nc("@title:tab", "Plugins") - active: toolbox.viewCategory == "plugin" && enabled - enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored" - onClicked: - { - toolbox.filterModelByProp("packages", "type", "plugin") - toolbox.viewCategory = "plugin" - toolbox.viewPage = "overview" - } - } - - ToolboxTabButton - { - id: materialsTabButton - text: catalog.i18nc("@title:tab", "Materials") - active: toolbox.viewCategory == "material" && enabled - enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored" - onClicked: - { - toolbox.filterModelByProp("authors", "package_types", "material") - toolbox.viewCategory = "material" - toolbox.viewPage = "overview" - } - } - - ToolboxTabButton - { - id: installedTabButton - text: catalog.i18nc("@title:tab", "Installed") - active: toolbox.viewCategory == "installed" - enabled: !toolbox.isDownloading - onClicked: toolbox.viewCategory = "installed" - width: UM.Theme.getSize("toolbox_header_tab").width + marketplaceNotificationIcon.width - UM.Theme.getSize("default_margin").width - } - - - } - - Cura.NotificationIcon - { - id: marketplaceNotificationIcon - visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 - anchors.right: bar.right - labelText: - { - const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length - return itemCount > 9 ? "9+" : itemCount - } - } - - - UM.TooltipArea - { - id: webMarketplaceButtonTooltipArea - width: childrenRect.width - height: parent.height - text: catalog.i18nc("@info:tooltip", "Go to Web Marketplace") - anchors - { - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - verticalCenter: parent.verticalCenter - } - acceptedButtons: Qt.LeftButton - onClicked: Qt.openUrlExternally(toolbox.getWebMarketplaceUrl("plugins") + "?utm_source=cura&utm_medium=software&utm_campaign=marketplace-button") - UM.RecolorImage - { - id: cloudMarketplaceButton - source: "../../images/Shop.svg" - color: UM.Theme.getColor(webMarketplaceButtonTooltipArea.containsMouse ? "primary" : "text") - height: parent.height / 2 - width: height - anchors.verticalCenter: parent.verticalCenter - sourceSize.width: width - sourceSize.height: height - } - } - - ToolboxShadow - { - anchors.top: bar.bottom - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/components/ToolboxInstalledTile.qml deleted file mode 100644 index e5c94fc996..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxInstalledTile.qml +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM - -Item -{ - height: UM.Theme.getSize("toolbox_installed_tile").height - width: parent.width - property bool isEnabled: true - - Rectangle - { - color: UM.Theme.getColor("lining") - width: parent.width - height: Math.floor(UM.Theme.getSize("default_lining").height) - anchors.bottom: parent.top - visible: index != 0 - } - Row - { - id: tileRow - height: parent.height - width: parent.width - spacing: UM.Theme.getSize("default_margin").width - topPadding: UM.Theme.getSize("default_margin").height - - CheckBox - { - id: disableButton - anchors.verticalCenter: pluginInfo.verticalCenter - checked: isEnabled - visible: model.type == "plugin" - width: visible ? UM.Theme.getSize("checkbox").width : 0 - enabled: !toolbox.isDownloading - style: UM.Theme.styles.checkbox - onClicked: toolbox.isEnabled(model.id) ? toolbox.disable(model.id) : toolbox.enable(model.id) - } - Column - { - id: pluginInfo - topPadding: UM.Theme.getSize("narrow_margin").height - property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text") - width: Math.floor(tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0))) - Label - { - text: model.name - width: parent.width - maximumLineCount: 1 - elide: Text.ElideRight - wrapMode: Text.WordWrap - font: UM.Theme.getFont("large_bold") - color: pluginInfo.color - renderType: Text.NativeRendering - } - Label - { - text: model.description - font: UM.Theme.getFont("default") - maximumLineCount: 3 - elide: Text.ElideRight - width: parent.width - wrapMode: Text.WordWrap - color: pluginInfo.color - renderType: Text.NativeRendering - } - } - Column - { - id: authorInfo - width: Math.floor(UM.Theme.getSize("toolbox_action_button").width * 1.25) - - Label - { - text: - { - if (model.author_email) - { - return "" + model.author_name + "" - } - else - { - return model.author_name - } - } - font: UM.Theme.getFont("medium") - width: parent.width - height: Math.floor(UM.Theme.getSize("toolbox_property_label").height) - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin") - color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining") - linkColor: UM.Theme.getColor("text_link") - renderType: Text.NativeRendering - } - - Label - { - text: model.version - font: UM.Theme.getFont("default") - width: parent.width - height: UM.Theme.getSize("toolbox_property_label").height - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - renderType: Text.NativeRendering - } - } - ToolboxInstalledTileActions - { - id: pluginActions - } - Connections - { - target: toolbox - function onToolboxEnabledChanged() { isEnabled = toolbox.isEnabled(model.id) } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/components/ToolboxInstalledTileActions.qml deleted file mode 100644 index 1726497c00..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxInstalledTileActions.qml +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.1 as UM - -import Cura 1.1 as Cura - -Column -{ - property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1 - property bool canDowngrade: false - property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn - width: UM.Theme.getSize("toolbox_action_button").width - spacing: UM.Theme.getSize("narrow_margin").height - - Label - { - visible: !model.is_installed - text: catalog.i18nc("@label", "Will install upon restarting") - color: UM.Theme.getColor("lining") - font: UM.Theme.getFont("default") - wrapMode: Text.WordWrap - width: parent.width - renderType: Text.NativeRendering - } - - ToolboxProgressButton - { - id: updateButton - active: toolbox.isDownloading && toolbox.activePackage == model - readyLabel: catalog.i18nc("@action:button", "Update") - activeLabel: catalog.i18nc("@action:button", "Updating") - completeLabel: catalog.i18nc("@action:button", "Updated") - onReadyAction: - { - toolbox.activePackage = model - toolbox.update(model.id) - } - onActiveAction: toolbox.cancelDownload() - - // Don't allow installing while another download is running - enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired - opacity: enabled ? 1.0 : 0.5 - visible: canUpdate - } - - Label - { - wrapMode: Text.WordWrap - text: catalog.i18nc("@label:The string between and is the highlighted link", "Log in is required to update") - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - visible: loginRequired - width: updateButton.width - renderType: Text.NativeRendering - - MouseArea - { - anchors.fill: parent - onClicked: Cura.API.account.login() - } - } - - Cura.SecondaryButton - { - id: removeButton - text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall") - visible: !model.is_bundled && model.is_installed - enabled: !toolbox.isDownloading - - width: UM.Theme.getSize("toolbox_action_button").width - height: UM.Theme.getSize("toolbox_action_button").height - - fixedWidthMode: true - - onClicked: toolbox.checkPackageUsageAndUninstall(model.id) - Connections - { - target: toolbox - function onMetadataChanged() - { - canDowngrade = toolbox.canDowngrade(model.id) - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/components/ToolboxProgressButton.qml deleted file mode 100644 index 40d6c1af47..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxProgressButton.qml +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 - -import UM 1.1 as UM -import Cura 1.0 as Cura - - -Cura.PrimaryButton -{ - id: button - - property var active: false - property var complete: false - - property var readyLabel: catalog.i18nc("@action:button", "Install") - property var activeLabel: catalog.i18nc("@action:button", "Cancel") - property var completeLabel: catalog.i18nc("@action:button", "Installed") - - signal readyAction() // Action when button is ready and clicked (likely install) - signal activeAction() // Action when button is active and clicked (likely cancel) - signal completeAction() // Action when button is complete and clicked (likely go to installed) - - width: UM.Theme.getSize("toolbox_action_button").width - height: UM.Theme.getSize("toolbox_action_button").height - fixedWidthMode: true - text: - { - if (complete) - { - return completeLabel - } - else if (active) - { - return activeLabel - } - else - { - return readyLabel - } - } - onClicked: - { - if (complete) - { - completeAction() - } - else if (active) - { - activeAction() - } - else - { - readyAction() - } - } - busy: active -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxShadow.qml b/plugins/Toolbox/resources/qml/components/ToolboxShadow.qml deleted file mode 100644 index 0f2f98beb9..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxShadow.qml +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.2 - -Rectangle -{ - property bool reversed: false - width: parent.width - height: 8 - gradient: Gradient - { - GradientStop - { - position: reversed ? 1.0 : 0.0 - color: reversed ? Qt.rgba(0,0,0,0.05) : Qt.rgba(0,0,0,0.2) - } - GradientStop - { - position: reversed ? 0.0 : 1.0 - color: Qt.rgba(0,0,0,0) - } - } -} diff --git a/plugins/Toolbox/resources/qml/components/ToolboxTabButton.qml b/plugins/Toolbox/resources/qml/components/ToolboxTabButton.qml deleted file mode 100644 index 7a7d2be48a..0000000000 --- a/plugins/Toolbox/resources/qml/components/ToolboxTabButton.qml +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 -import UM 1.1 as UM - -Button -{ - id: control - property bool active: false - - implicitWidth: UM.Theme.getSize("toolbox_header_tab").width - implicitHeight: UM.Theme.getSize("toolbox_header_tab").height - - background: Item - { - id: backgroundItem - Rectangle - { - id: highlight - - visible: control.active - color: UM.Theme.getColor("primary") - anchors.bottom: parent.bottom - width: parent.width - height: UM.Theme.getSize("toolbox_header_highlight").height - } - } - - contentItem: Label - { - id: label - text: control.text - color: UM.Theme.getColor("toolbox_header_button_text_inactive") - font: UM.Theme.getFont("medium") - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - - renderType: Text.NativeRendering - } - - states: - [ - State - { - name: "disabled" - when: !control.enabled - PropertyChanges - { - target: label - font: UM.Theme.getFont("default_italic") - } - }, - State - { - name: "active" - when: control.active - PropertyChanges - { - target: label - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("action_button_text") - } - } - ] -} \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml deleted file mode 100644 index b33036847b..0000000000 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2020 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Window 2.2 -import QtQuick.Controls 2.3 - -import UM 1.1 as UM -import Cura 1.6 as Cura - - -UM.Dialog{ - visible: true - title: catalog.i18nc("@title", "Changes from your account") - width: UM.Theme.getSize("popup_dialog").width - height: UM.Theme.getSize("popup_dialog").height - minimumWidth: width - maximumWidth: minimumWidth - minimumHeight: height - maximumHeight: minimumHeight - margin: 0 - - property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next") - - Rectangle - { - id: root - anchors.fill: parent - color: UM.Theme.getColor("main_background") - - UM.I18nCatalog - { - id: catalog - name: "cura" - } - - ScrollView - { - width: parent.width - height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom - clip: true - - Column - { - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width - - // Compatible packages - Label - { - font: UM.Theme.getFont("default") - text: catalog.i18nc("@label", "The following packages will be added:") - visible: subscribedPackagesModel.hasCompatiblePackages - color: UM.Theme.getColor("text") - height: contentHeight + UM.Theme.getSize("default_margin").height - } - Repeater - { - model: subscribedPackagesModel - Component - { - Item - { - width: parent.width - property int lineHeight: 60 - visible: model.is_compatible - height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here - Image - { - id: packageIcon - source: model.icon_url || "../../images/placeholder.svg" - height: lineHeight - width: height - sourceSize.height: height - sourceSize.width: width - mipmap: true - fillMode: Image.PreserveAspectFit - } - Label - { - text: model.display_name - font: UM.Theme.getFont("medium_bold") - anchors.left: packageIcon.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.verticalCenter: packageIcon.verticalCenter - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - } - } - - // Incompatible packages - Label - { - font: UM.Theme.getFont("default") - text: catalog.i18nc("@label", "The following packages can not be installed because of an incompatible Cura version:") - visible: subscribedPackagesModel.hasIncompatiblePackages - color: UM.Theme.getColor("text") - height: contentHeight + UM.Theme.getSize("default_margin").height - } - Repeater - { - model: subscribedPackagesModel - Component - { - Item - { - width: parent.width - property int lineHeight: 60 - visible: !model.is_compatible && !model.is_dismissed - height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here - Image - { - id: packageIcon - source: model.icon_url || "../../images/placeholder.svg" - height: lineHeight - width: height - sourceSize.height: height - sourceSize.width: width - mipmap: true - fillMode: Image.PreserveAspectFit - } - Label - { - text: model.display_name - font: UM.Theme.getFont("medium_bold") - anchors.left: packageIcon.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.verticalCenter: packageIcon.verticalCenter - color: UM.Theme.getColor("text") - elide: Text.ElideRight - } - } - } - } - } - - } // End of ScrollView - - Cura.PrimaryButton - { - id: nextButton - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: UM.Theme.getSize("default_margin").height - text: actionButtonText - onClicked: accept() - leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width - rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width - } - } -} diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxConfirmUninstallResetDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxConfirmUninstallResetDialog.qml deleted file mode 100644 index 1b5e4d1d46..0000000000 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxConfirmUninstallResetDialog.qml +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.1 -import QtQuick.Controls.Styles 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 -import QtQuick.Window 2.1 - -import UM 1.3 as UM -import Cura 1.0 as Cura - - -UM.Dialog -{ - // This dialog asks the user to confirm he/she wants to uninstall materials/pprofiles which are currently in use - id: base - - title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall - width: 450 * screenScaleFactor - height: 50 * screenScaleFactor + dialogText.height + buttonBar.height - - maximumWidth: 450 * screenScaleFactor - maximumHeight: 450 * screenScaleFactor - minimumWidth: 450 * screenScaleFactor - minimumHeight: 150 * screenScaleFactor - - modality: Qt.WindowModal - - Column - { - UM.I18nCatalog { id: catalog; name: "cura" } - - anchors - { - fill: parent - leftMargin: Math.round(20 * screenScaleFactor) - rightMargin: Math.round(20 * screenScaleFactor) - topMargin: Math.round(10 * screenScaleFactor) - bottomMargin: Math.round(10 * screenScaleFactor) - } - spacing: Math.round(15 * screenScaleFactor) - - Label - { - id: dialogText - text: - { - var base_text = catalog.i18nc("@text:window", "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults.") - var materials_text = catalog.i18nc("@text:window", "Materials") - var qualities_text = catalog.i18nc("@text:window", "Profiles") - var machines_with_materials = toolbox.uninstallUsedMaterials - var machines_with_qualities = toolbox.uninstallUsedQualities - if (machines_with_materials != "") - { - base_text += "\n\n" + materials_text +": \n" + machines_with_materials - } - if (machines_with_qualities != "") - { - base_text += "\n\n" + qualities_text + ": \n" + machines_with_qualities - } - return base_text - } - anchors.left: parent.left - anchors.right: parent.right - font: UM.Theme.getFont("default") - wrapMode: Text.WordWrap - renderType: Text.NativeRendering - } - - // Buttons - Item { - id: buttonBar - anchors.right: parent.right - anchors.left: parent.left - height: childrenRect.height - - Button { - id: cancelButton - text: catalog.i18nc("@action:button", "Cancel") - anchors.right: confirmButton.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - isDefault: true - onClicked: toolbox.closeConfirmResetDialog() - } - - Button { - id: confirmButton - text: catalog.i18nc("@action:button", "Confirm") - anchors.right: parent.right - onClicked: toolbox.resetMaterialsQualitiesAndUninstall() - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml deleted file mode 100644 index 9219f4ed32..0000000000 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Dialogs 1.1 -import QtQuick.Window 2.2 -import QtQuick.Controls 2.3 -import QtQuick.Layouts 1.3 -import QtQuick.Controls.Styles 1.4 - -import UM 1.1 as UM -import Cura 1.6 as Cura - -UM.Dialog -{ - id: licenseDialog - title: licenseModel.dialogTitle - minimumWidth: UM.Theme.getSize("license_window_minimum").width - minimumHeight: UM.Theme.getSize("license_window_minimum").height - width: minimumWidth - height: minimumHeight - backgroundColor: UM.Theme.getColor("main_background") - margin: screenScaleFactor * 10 - - ColumnLayout - { - anchors.fill: parent - spacing: UM.Theme.getSize("thick_margin").height - - UM.I18nCatalog{id: catalog; name: "cura"} - - Label - { - id: licenseHeader - Layout.fillWidth: true - text: catalog.i18nc("@label", "You need to accept the license to install the package") - color: UM.Theme.getColor("text") - wrapMode: Text.Wrap - renderType: Text.NativeRendering - } - - Row { - id: packageRow - - Layout.fillWidth: true - height: childrenRect.height - spacing: UM.Theme.getSize("default_margin").width - leftPadding: UM.Theme.getSize("narrow_margin").width - - Image - { - id: icon - width: 30 * screenScaleFactor - height: width - sourceSize.width: width - sourceSize.height: height - fillMode: Image.PreserveAspectFit - source: licenseModel.iconUrl || "../../images/placeholder.svg" - mipmap: true - } - - Label - { - id: packageName - text: licenseModel.packageName - color: UM.Theme.getColor("text") - font.bold: true - anchors.verticalCenter: icon.verticalCenter - height: contentHeight - wrapMode: Text.Wrap - renderType: Text.NativeRendering - } - - - } - - Cura.ScrollableTextArea - { - - Layout.fillWidth: true - Layout.fillHeight: true - anchors.topMargin: UM.Theme.getSize("default_margin").height - - textArea.text: licenseModel.licenseText - textArea.readOnly: true - } - - } - rightButtons: - [ - Cura.PrimaryButton - { - leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width - rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width - - text: licenseModel.acceptButtonText - onClicked: { handler.onLicenseAccepted() } - } - ] - - leftButtons: - [ - Cura.SecondaryButton - { - id: declineButton - text: licenseModel.declineButtonText - onClicked: { handler.onLicenseDeclined() } - } - ] -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxAuthorPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxAuthorPage.qml deleted file mode 100644 index 2fa4224388..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxAuthorPage.qml +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.5 as UM - -import "../components" - -Item -{ - id: page - property var details: base.selection || {} - anchors.fill: parent - ToolboxBackColumn - { - id: sidebar - } - Item - { - id: header - anchors - { - left: sidebar.right - right: parent.right - rightMargin: UM.Theme.getSize("wide_margin").width - } - height: UM.Theme.getSize("toolbox_detail_header").height - Image - { - id: thumbnail - width: UM.Theme.getSize("toolbox_thumbnail_medium").width - height: UM.Theme.getSize("toolbox_thumbnail_medium").height - fillMode: Image.PreserveAspectFit - source: details && details.icon_url ? details.icon_url : "../../images/placeholder.svg" - mipmap: true - anchors - { - top: parent.top - left: parent.left - leftMargin: UM.Theme.getSize("wide_margin").width - topMargin: UM.Theme.getSize("wide_margin").height - } - } - - Label - { - id: title - anchors - { - top: thumbnail.top - left: thumbnail.right - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("wide_margin").width - bottomMargin: UM.Theme.getSize("default_margin").height - } - text: details && details.name ? details.name : "" - font: UM.Theme.getFont("large_bold") - color: UM.Theme.getColor("text_medium") - wrapMode: Text.WordWrap - width: parent.width - height: UM.Theme.getSize("toolbox_property_label").height - renderType: Text.NativeRendering - } - Label - { - id: description - text: details && details.description ? details.description : "" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - anchors - { - top: title.bottom - left: title.left - topMargin: UM.Theme.getSize("default_margin").height - } - renderType: Text.NativeRendering - } - Column - { - id: properties - anchors - { - top: description.bottom - left: description.left - topMargin: UM.Theme.getSize("default_margin").height - } - spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) - width: childrenRect.width - - Label - { - text: catalog.i18nc("@label", "Website") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - Label - { - text: catalog.i18nc("@label", "Email") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - } - Column - { - id: values - anchors - { - top: description.bottom - left: properties.right - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - topMargin: UM.Theme.getSize("default_margin").height - } - spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) - - Label - { - text: - { - if (details && details.website) - { - return "" + details.website + "" - } - return "" - } - width: parent.width - elide: Text.ElideRight - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - onLinkActivated: UM.UrlUtil.openUrl(link, ["https", "http"]) - renderType: Text.NativeRendering - } - - Label - { - text: - { - if (details && details.email) - { - return "" + details.email + "" - } - return "" - } - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - onLinkActivated: Qt.openUrlExternally(link) - renderType: Text.NativeRendering - } - } - Rectangle - { - color: UM.Theme.getColor("lining") - width: parent.width - height: UM.Theme.getSize("default_lining").height - anchors.bottom: parent.bottom - } - } - ToolboxDetailList - { - anchors - { - top: header.bottom - bottom: page.bottom - left: header.left - right: page.right - } - } -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxDetailPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxDetailPage.qml deleted file mode 100644 index 645b77a8c9..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxDetailPage.qml +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.5 as UM - -import Cura 1.1 as Cura - -import "../components" - -Item -{ - id: page - property var details: base.selection || {} - anchors.fill: parent - ToolboxBackColumn - { - id: sidebar - } - Item - { - id: header - anchors - { - left: sidebar.right - right: parent.right - rightMargin: UM.Theme.getSize("wide_margin").width - } - height: childrenRect.height + 3 * UM.Theme.getSize("default_margin").width - Rectangle - { - id: thumbnail - width: UM.Theme.getSize("toolbox_thumbnail_medium").width - height: UM.Theme.getSize("toolbox_thumbnail_medium").height - anchors - { - top: parent.top - left: parent.left - leftMargin: UM.Theme.getSize("wide_margin").width - topMargin: UM.Theme.getSize("wide_margin").height - } - color: UM.Theme.getColor("main_background") - Image - { - anchors.fill: parent - fillMode: Image.PreserveAspectFit - source: details === null ? "" : (details.icon_url || "../../images/placeholder.svg") - mipmap: true - height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height - width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height - sourceSize.height: height - sourceSize.width: width - } - } - - Label - { - id: title - anchors - { - top: thumbnail.top - left: thumbnail.right - leftMargin: UM.Theme.getSize("default_margin").width - } - text: details === null ? "" : (details.name || "") - font: UM.Theme.getFont("large_bold") - color: UM.Theme.getColor("text") - width: contentWidth - height: contentHeight - renderType: Text.NativeRendering - } - - Column - { - id: properties - anchors - { - top: title.bottom - left: title.left - topMargin: UM.Theme.getSize("default_margin").height - } - spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) - width: childrenRect.width - height: childrenRect.height - Label - { - text: catalog.i18nc("@label", "Version") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - Label - { - text: catalog.i18nc("@label", "Last updated") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - Label - { - text: catalog.i18nc("@label", "Brand") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - Label - { - text: catalog.i18nc("@label", "Downloads") + ":" - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text_medium") - renderType: Text.NativeRendering - } - } - Column - { - id: values - anchors - { - top: title.bottom - left: properties.right - leftMargin: UM.Theme.getSize("default_margin").width - topMargin: UM.Theme.getSize("default_margin").height - } - spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) - height: childrenRect.height - Label - { - text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown")) - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - renderType: Text.NativeRendering - } - Label - { - text: - { - if (details === null) - { - return "" - } - var date = new Date(details.last_updated) - return date.toLocaleString(UM.Preferences.getValue("general/language")) - } - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - renderType: Text.NativeRendering - } - Label - { - text: - { - if (details === null) - { - return "" - } - else - { - return "" + details.author_name + "" - } - } - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - linkColor: UM.Theme.getColor("text_link") - onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"]) - renderType: Text.NativeRendering - } - Label - { - text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown")) - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - renderType: Text.NativeRendering - } - } - } - ToolboxDetailList - { - anchors - { - top: header.bottom - bottom: page.bottom - left: header.left - right: page.right - } - } -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxDownloadsPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxDownloadsPage.qml deleted file mode 100644 index 9be8cbe2b9..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxDownloadsPage.qml +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 -import UM 1.1 as UM - -import "../components" - -ScrollView -{ - clip: true - width: parent.width - height: parent.height - contentHeight: mainColumn.height - - Column - { - id: mainColumn - width: base.width - spacing: UM.Theme.getSize("default_margin").height - - ToolboxDownloadsShowcase - { - id: showcase - width: parent.width - } - - ToolboxDownloadsGrid - { - id: allPlugins - width: parent.width - heading: toolbox.viewCategory === "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins") - model: toolbox.viewCategory === "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel - } - - ToolboxDownloadsGrid - { - id: genericMaterials - visible: toolbox.viewCategory === "material" - width: parent.width - heading: catalog.i18nc("@label", "Generic Materials") - model: toolbox.materialsGenericModel - } - } -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxErrorPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxErrorPage.qml deleted file mode 100644 index e57e63dbb9..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxErrorPage.qml +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 - -Rectangle -{ - id: page - width: parent.width - height: parent.height - color: "transparent" - Label - { - text: catalog.i18nc("@info", "Could not connect to the Cura Package database. Please check your connection.") - anchors - { - centerIn: parent - } - renderType: Text.NativeRendering - } -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxInstalledPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxInstalledPage.qml deleted file mode 100644 index fa7bd24c9d..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxInstalledPage.qml +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 - -import UM 1.1 as UM - -import "../components" - -ScrollView -{ - id: page - clip: true - width: parent.width - height: parent.height - - Column - { - width: page.width - spacing: UM.Theme.getSize("default_margin").height - padding: UM.Theme.getSize("wide_margin").width - height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height - - Label - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - text: catalog.i18nc("@title:tab", "Installed plugins") - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - - Rectangle - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - id: installedPlugins - color: "transparent" - height: childrenRect.height + UM.Theme.getSize("default_margin").width - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - Column - { - anchors - { - top: parent.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - Repeater - { - id: pluginList - model: toolbox.pluginsInstalledModel - delegate: ToolboxInstalledTile { } - } - } - Label - { - visible: toolbox.pluginsInstalledModel.count < 1 - padding: UM.Theme.getSize("default_margin").width - text: catalog.i18nc("@info", "No plugin has been installed.") - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("lining") - renderType: Text.NativeRendering - } - } - - Label - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - text: catalog.i18nc("@title:tab", "Installed materials") - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - - Rectangle - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - id: installedMaterials - color: "transparent" - height: childrenRect.height + UM.Theme.getSize("default_margin").width - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - Column - { - anchors - { - top: parent.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - Repeater - { - id: installedMaterialsList - model: toolbox.materialsInstalledModel - delegate: ToolboxInstalledTile { } - } - } - Label - { - visible: toolbox.materialsInstalledModel.count < 1 - padding: UM.Theme.getSize("default_margin").width - text: catalog.i18nc("@info", "No material has been installed.") - color: UM.Theme.getColor("lining") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - } - - Label - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - text: catalog.i18nc("@title:tab", "Bundled plugins") - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - - Rectangle - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - id: bundledPlugins - color: "transparent" - height: childrenRect.height + UM.Theme.getSize("default_margin").width - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - Column - { - anchors - { - top: parent.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - Repeater - { - id: bundledPluginsList - model: toolbox.pluginsBundledModel - delegate: ToolboxInstalledTile { } - } - } - } - - Label - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - text: catalog.i18nc("@title:tab", "Bundled materials") - color: UM.Theme.getColor("text_medium") - font: UM.Theme.getFont("medium") - renderType: Text.NativeRendering - } - - Rectangle - { - anchors - { - left: parent.left - right: parent.right - margins: parent.padding - } - id: bundledMaterials - color: "transparent" - height: childrenRect.height + UM.Theme.getSize("default_margin").width - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - Column - { - anchors - { - top: parent.top - right: parent.right - left: parent.left - margins: UM.Theme.getSize("default_margin").width - } - Repeater - { - id: bundledMaterialsList - model: toolbox.materialsBundledModel - delegate: ToolboxInstalledTile {} - } - } - } - } -} diff --git a/plugins/Toolbox/resources/qml/pages/ToolboxLoadingPage.qml b/plugins/Toolbox/resources/qml/pages/ToolboxLoadingPage.qml deleted file mode 100644 index a30af6b335..0000000000 --- a/plugins/Toolbox/resources/qml/pages/ToolboxLoadingPage.qml +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import UM 1.3 as UM - -Rectangle -{ - id: page - width: parent.width - height: parent.height - color: "transparent" - Label - { - text: catalog.i18nc("@info", "Fetching packages...") - color: UM.Theme.getColor("text") - anchors - { - centerIn: parent - } - renderType: Text.NativeRendering - } -} diff --git a/plugins/Toolbox/resources/qml/pages/WelcomePage.qml b/plugins/Toolbox/resources/qml/pages/WelcomePage.qml deleted file mode 100644 index 04110cbc0f..0000000000 --- a/plugins/Toolbox/resources/qml/pages/WelcomePage.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtQuick.Window 2.2 - -import UM 1.3 as UM -import Cura 1.1 as Cura - -Column -{ - id: welcomePage - spacing: UM.Theme.getSize("wide_margin").height - width: parent.width - height: childrenRect.height - anchors.centerIn: parent - - Label - { - id: welcomeTextLabel - text: catalog.i18nc("@description", "Please sign in to get verified plugins and materials for Ultimaker Cura Enterprise") - width: Math.round(parent.width / 2) - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Label.WordWrap - renderType: Text.NativeRendering - } - - Cura.PrimaryButton - { - id: loginButton - width: UM.Theme.getSize("account_button").width - height: UM.Theme.getSize("account_button").height - anchors.horizontalCenter: parent.horizontalCenter - text: catalog.i18nc("@button", "Sign in") - onClicked: Cura.API.account.login() - fixedWidthMode: true - } -} - diff --git a/plugins/Toolbox/src/AuthorsModel.py b/plugins/Toolbox/src/AuthorsModel.py deleted file mode 100644 index 04c8ed3a40..0000000000 --- a/plugins/Toolbox/src/AuthorsModel.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import re -from typing import Dict, List, Optional, Union, cast - -from PyQt5.QtCore import Qt, pyqtProperty - -from UM.Qt.ListModel import ListModel - - -class AuthorsModel(ListModel): - """Model that holds cura packages. - - By setting the filter property the instances held by this model can be changed. - """ - - def __init__(self, parent = None) -> None: - super().__init__(parent) - - self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]] - - self.addRoleName(Qt.UserRole + 1, "id") - self.addRoleName(Qt.UserRole + 2, "name") - self.addRoleName(Qt.UserRole + 3, "email") - self.addRoleName(Qt.UserRole + 4, "website") - self.addRoleName(Qt.UserRole + 5, "package_count") - self.addRoleName(Qt.UserRole + 6, "package_types") - self.addRoleName(Qt.UserRole + 7, "icon_url") - self.addRoleName(Qt.UserRole + 8, "description") - - # List of filters for queries. The result is the union of the each list of results. - self._filter = {} # type: Dict[str, str] - - def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]): - if self._metadata != data: - self._metadata = data - self._update() - - def _update(self) -> None: - items = [] # type: List[Dict[str, Union[str, List[str], int, None]]] - if not self._metadata: - self.setItems(items) - return - - for author in self._metadata: - items.append({ - "id": author.get("author_id"), - "name": author.get("display_name"), - "email": author.get("email"), - "website": author.get("website"), - "package_count": author.get("package_count", 0), - "package_types": author.get("package_types", []), - "icon_url": author.get("icon_url"), - "description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", "")) - }) - - # Filter on all the key-word arguments. - for key, value in self._filter.items(): - if key == "package_types": - key_filter = lambda item, value = value: value in item["package_types"] # type: ignore - elif "*" in value: - key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore - else: - key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore - items = filter(key_filter, items) # type: ignore - - # Execute all filters. - filtered_items = list(items) - - filtered_items.sort(key = lambda k: cast(str, k["name"])) - self.setItems(filtered_items) - - def setFilter(self, filter_dict: Dict[str, str]) -> None: - """Set the filter of this model based on a string. - - :param filter_dict: Dictionary to do the filtering by. - """ - if filter_dict != self._filter: - self._filter = filter_dict - self._update() - - @pyqtProperty("QVariantMap", fset = setFilter, constant = True) - def filter(self) -> Dict[str, str]: - return self._filter - - # Check to see if a container matches with a regular expression - def _matchRegExp(self, metadata, property_name, value): - if property_name not in metadata: - return False - value = re.escape(value) #Escape for regex patterns. - value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match. - if self._ignore_case: - value_pattern = re.compile(value, re.IGNORECASE) - else: - value_pattern = re.compile(value) - - return value_pattern.match(str(metadata[property_name])) - - # Check to see if a container matches with a string - def _matchString(self, metadata, property_name, value): - if property_name not in metadata: - return False - return value.lower() == str(metadata[property_name]).lower() diff --git a/plugins/Toolbox/src/CloudApiModel.py b/plugins/Toolbox/src/CloudApiModel.py deleted file mode 100644 index bef37d8173..0000000000 --- a/plugins/Toolbox/src/CloudApiModel.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Union - -from cura import ApplicationMetadata -from cura.UltimakerCloud import UltimakerCloudConstants - - -class CloudApiModel: - sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] - cloud_api_version = UltimakerCloudConstants.CuraCloudAPIVersion # type: str - cloud_api_root = UltimakerCloudConstants.CuraCloudAPIRoot # type: str - api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format( - cloud_api_root = cloud_api_root, - cloud_api_version = cloud_api_version, - sdk_version = sdk_version - ) # type: str - - # https://api.ultimaker.com/cura-packages/v1/user/packages - api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format( - cloud_api_root=cloud_api_root, - cloud_api_version=cloud_api_version, - ) - - @classmethod - def userPackageUrl(cls, package_id: str) -> str: - """https://api.ultimaker.com/cura-packages/v1/user/packages/{package_id}""" - - return (CloudApiModel.api_url_user_packages + "/{package_id}").format( - package_id=package_id - ) diff --git a/plugins/Toolbox/src/CloudSync/CloudApiClient.py b/plugins/Toolbox/src/CloudSync/CloudApiClient.py deleted file mode 100644 index 9543ec012e..0000000000 --- a/plugins/Toolbox/src/CloudSync/CloudApiClient.py +++ /dev/null @@ -1,52 +0,0 @@ -from UM.Logger import Logger -from UM.TaskManagement.HttpRequestManager import HttpRequestManager -from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope -from cura.CuraApplication import CuraApplication -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope -from ..CloudApiModel import CloudApiModel - - -class CloudApiClient: - """Manages Cloud subscriptions - - When a package is added to a user's account, the user is 'subscribed' to that package. - Whenever the user logs in on another instance of Cura, these subscriptions can be used to sync the user's plugins - - Singleton: use CloudApiClient.getInstance() instead of CloudApiClient() - """ - - __instance = None - - @classmethod - def getInstance(cls, app: CuraApplication): - if not cls.__instance: - cls.__instance = CloudApiClient(app) - return cls.__instance - - def __init__(self, app: CuraApplication) -> None: - if self.__instance is not None: - raise RuntimeError("This is a Singleton. use getInstance()") - - self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) # type: JsonDecoratorScope - - app.getPackageManager().packageInstalled.connect(self._onPackageInstalled) - - def unsubscribe(self, package_id: str) -> None: - url = CloudApiModel.userPackageUrl(package_id) - HttpRequestManager.getInstance().delete(url = url, scope = self._scope) - - def _subscribe(self, package_id: str) -> None: - """You probably don't want to use this directly. All installed packages will be automatically subscribed.""" - - Logger.debug("Subscribing to using the Old Toolbox {}", package_id) - data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, CloudApiModel.sdk_version) - HttpRequestManager.getInstance().put( - url = CloudApiModel.api_url_user_packages, - data = data.encode(), - scope = self._scope - ) - - def _onPackageInstalled(self, package_id: str): - if CuraApplication.getInstance().getCuraAPI().account.isLoggedIn: - # We might already be subscribed, but checking would take one extra request. Instead, simply subscribe - self._subscribe(package_id) diff --git a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py deleted file mode 100644 index 4c1818e4ee..0000000000 --- a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2020 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import json -from typing import List, Dict, Any, Set -from typing import Optional - -from PyQt5.QtCore import QObject -from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest - -from UM import i18nCatalog -from UM.Logger import Logger -from UM.Message import Message -from UM.Signal import Signal -from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope -from cura.API.Account import SyncState -from cura.CuraApplication import CuraApplication, ApplicationMetadata -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope -from .SubscribedPackagesModel import SubscribedPackagesModel -from ..CloudApiModel import CloudApiModel - - -class CloudPackageChecker(QObject): - - SYNC_SERVICE_NAME = "CloudPackageChecker" - - def __init__(self, application: CuraApplication) -> None: - super().__init__() - - self.discrepancies = Signal() # Emits SubscribedPackagesModel - self._application = application # type: CuraApplication - self._scope = JsonDecoratorScope(UltimakerCloudScope(application)) - self._model = SubscribedPackagesModel() - self._message = None # type: Optional[Message] - - self._application.initializationFinished.connect(self._onAppInitialized) - self._i18n_catalog = i18nCatalog("cura") - self._sdk_version = ApplicationMetadata.CuraSDKVersion - self._last_notified_packages = set() # type: Set[str] - """Packages for which a notification has been shown. No need to bother the user twice for equal content""" - - # This is a plugin, so most of the components required are not ready when - # this is initialized. Therefore, we wait until the application is ready. - def _onAppInitialized(self) -> None: - self._package_manager = self._application.getPackageManager() - # initial check - self._getPackagesIfLoggedIn() - - self._application.getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged) - self._application.getCuraAPI().account.syncRequested.connect(self._getPackagesIfLoggedIn) - - def _onLoginStateChanged(self) -> None: - # reset session - self._last_notified_packages = set() - self._getPackagesIfLoggedIn() - - def _getPackagesIfLoggedIn(self) -> None: - if self._application.getCuraAPI().account.isLoggedIn: - self._getUserSubscribedPackages() - else: - self._hideSyncMessage() - - def _getUserSubscribedPackages(self) -> None: - self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SYNCING) - url = CloudApiModel.api_url_user_packages - self._application.getHttpRequestManager().get(url, - callback = self._onUserPackagesRequestFinished, - error_callback = self._onUserPackagesRequestFinished, - timeout=10, - scope = self._scope) - - def _onUserPackagesRequestFinished(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None: - if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: - Logger.log("w", - "Requesting user packages failed, response code %s while trying to connect to %s", - reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url()) - self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) - return - - try: - json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) - # Check for errors: - if "errors" in json_data: - for error in json_data["errors"]: - Logger.log("e", "%s", error["title"]) - self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) - return - self._handleCompatibilityData(json_data["data"]) - except json.decoder.JSONDecodeError: - Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace") - - self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS) - - def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None: - user_subscribed_packages = {plugin["package_id"] for plugin in subscribed_packages_payload} - user_installed_packages = self._package_manager.getAllInstalledPackageIDs() - - # We need to re-evaluate the dismissed packages - # (i.e. some package might got updated to the correct SDK version in the meantime, - # hence remove them from the Dismissed Incompatible list) - self._package_manager.reEvaluateDismissedPackages(subscribed_packages_payload, self._sdk_version) - user_dismissed_packages = self._package_manager.getDismissedPackages() - if user_dismissed_packages: - user_installed_packages.update(user_dismissed_packages) - - # We check if there are packages installed in Web Marketplace but not in Cura marketplace - package_discrepancy = list(user_subscribed_packages.difference(user_installed_packages)) - - if user_subscribed_packages != self._last_notified_packages: - # scenario: - # 1. user subscribes to a package - # 2. dismisses the license/unsubscribes - # 3. subscribes to the same package again - # in this scenario we want to notify the user again. To capture that there was a change during - # step 2, we clear the last_notified after step 2. This way, the user will be notified after - # step 3 even though the list of packages for step 1 and 3 are equal - self._last_notified_packages = set() - - if package_discrepancy: - account = self._application.getCuraAPI().account - account.setUpdatePackagesAction(lambda: self._onSyncButtonClicked(None, None)) - - if user_subscribed_packages == self._last_notified_packages: - # already notified user about these - return - - Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages") - self._model.addDiscrepancies(package_discrepancy) - self._model.initialize(self._package_manager, subscribed_packages_payload) - self._showSyncMessage() - self._last_notified_packages = user_subscribed_packages - - def _showSyncMessage(self) -> None: - """Show the message if it is not already shown""" - - if self._message is not None: - self._message.show() - return - - sync_message = Message(self._i18n_catalog.i18nc( - "@info:generic", - "Do you want to sync material and software packages with your account?"), - title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) - sync_message.addAction("sync", - name = self._i18n_catalog.i18nc("@action:button", "Sync"), - icon = "", - description = "Sync your plugins and print profiles to Ultimaker Cura.", - button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) - sync_message.actionTriggered.connect(self._onSyncButtonClicked) - sync_message.show() - self._message = sync_message - - def _hideSyncMessage(self) -> None: - """Hide the message if it is showing""" - - if self._message is not None: - self._message.hide() - self._message = None - - def _onSyncButtonClicked(self, sync_message: Optional[Message], sync_message_action: Optional[str]) -> None: - if sync_message is not None: - sync_message.hide() - self._hideSyncMessage() # Should be the same message, but also sets _message to None - self.discrepancies.emit(self._model) diff --git a/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py b/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py deleted file mode 100644 index cee2f6318a..0000000000 --- a/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -from typing import Optional - -from PyQt5.QtCore import QObject, pyqtSlot - -from UM.Qt.QtApplication import QtApplication -from UM.Signal import Signal -from .SubscribedPackagesModel import SubscribedPackagesModel - - -class DiscrepanciesPresenter(QObject): - """Shows a list of packages to be added or removed. The user can select which packages to (un)install. The user's - - choices are emitted on the `packageMutations` Signal. - """ - - def __init__(self, app: QtApplication) -> None: - super().__init__(app) - - self.packageMutations = Signal() # Emits SubscribedPackagesModel - - self._app = app - self._package_manager = app.getPackageManager() - self._dialog = None # type: Optional[QObject] - self._compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml" - - def present(self, plugin_path: str, model: SubscribedPackagesModel) -> None: - path = os.path.join(plugin_path, self._compatibility_dialog_path) - self._dialog = self._app.createQmlComponent(path, {"subscribedPackagesModel": model, "handler": self}) - assert self._dialog - self._dialog.accepted.connect(lambda: self._onConfirmClicked(model)) - - def _onConfirmClicked(self, model: SubscribedPackagesModel) -> None: - # If there are incompatible packages - automatically dismiss them - if model.getIncompatiblePackages(): - self._package_manager.dismissAllIncompatiblePackages(model.getIncompatiblePackages()) - # For now, all compatible packages presented to the user should be installed. - # Later, we might remove items for which the user unselected the package - if model.getCompatiblePackages(): - model.setItems(model.getCompatiblePackages()) - self.packageMutations.emit(model) diff --git a/plugins/Toolbox/src/CloudSync/DownloadPresenter.py b/plugins/Toolbox/src/CloudSync/DownloadPresenter.py deleted file mode 100644 index 8a5e763f3c..0000000000 --- a/plugins/Toolbox/src/CloudSync/DownloadPresenter.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (c) 2020 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import tempfile -from typing import Dict, List, Any - -from PyQt5.QtNetwork import QNetworkReply - -from UM.i18n import i18nCatalog -from UM.Logger import Logger -from UM.Message import Message -from UM.Signal import Signal -from UM.TaskManagement.HttpRequestManager import HttpRequestManager -from cura.CuraApplication import CuraApplication -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope -from .SubscribedPackagesModel import SubscribedPackagesModel - -i18n_catalog = i18nCatalog("cura") - - -class DownloadPresenter: - """Downloads a set of packages from the Ultimaker Cloud Marketplace - - use download() exactly once: should not be used for multiple sets of downloads since this class contains state - """ - - DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB - - def __init__(self, app: CuraApplication) -> None: - # Emits (Dict[str, str], List[str]) # (success_items, error_items) - # Dict{success_package_id, temp_file_path} - # List[errored_package_id] - self.done = Signal() - - self._app = app - self._scope = UltimakerCloudScope(app) - - self._started = False - self._progress_message = self._createProgressMessage() - self._progress = {} # type: Dict[str, Dict[str, Any]] # package_id, Dict - self._error = [] # type: List[str] # package_id - - def download(self, model: SubscribedPackagesModel) -> None: - if self._started: - Logger.error("Download already started. Create a new %s instead", self.__class__.__name__) - return - - manager = HttpRequestManager.getInstance() - for item in model.items: - package_id = item["package_id"] - - def finishedCallback(reply: QNetworkReply, pid = package_id) -> None: - self._onFinished(pid, reply) - - def progressCallback(rx: int, rt: int, pid = package_id) -> None: - self._onProgress(pid, rx, rt) - - def errorCallback(reply: QNetworkReply, error: QNetworkReply.NetworkError, pid = package_id) -> None: - self._onError(pid) - - request_data = manager.get( - item["download_url"], - callback = finishedCallback, - download_progress_callback = progressCallback, - error_callback = errorCallback, - scope = self._scope) - - self._progress[package_id] = { - "received": 0, - "total": 1, # make sure this is not considered done yet. Also divByZero-safe - "file_written": None, - "request_data": request_data, - "package_model": item - } - - self._started = True - self._progress_message.show() - - def abort(self) -> None: - manager = HttpRequestManager.getInstance() - for item in self._progress.values(): - manager.abortRequest(item["request_data"]) - - # Aborts all current operations and returns a copy with the same settings such as app and scope - def resetCopy(self) -> "DownloadPresenter": - self.abort() - self.done.disconnectAll() - return DownloadPresenter(self._app) - - def _createProgressMessage(self) -> Message: - return Message(i18n_catalog.i18nc("@info:generic", "Syncing..."), - lifetime = 0, - use_inactivity_timer = False, - progress = 0.0, - title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account")) - - def _onFinished(self, package_id: str, reply: QNetworkReply) -> None: - self._progress[package_id]["received"] = self._progress[package_id]["total"] - - try: - with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file: - bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) - while bytes_read: - temp_file.write(bytes_read) - bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) - self._app.processEvents() - self._progress[package_id]["file_written"] = temp_file.name - except IOError as e: - Logger.logException("e", "Failed to write downloaded package to temp file", e) - self._onError(package_id) - temp_file.close() - - self._checkDone() - - def _onProgress(self, package_id: str, rx: int, rt: int) -> None: - self._progress[package_id]["received"] = rx - self._progress[package_id]["total"] = rt - - received = 0 - total = 0 - for item in self._progress.values(): - received += item["received"] - total += item["total"] - - if total == 0: # Total download size is 0, or unknown, or there are no progress items at all. - self._progress_message.setProgress(100.0) - return - - self._progress_message.setProgress(100.0 * (received / total)) # [0 .. 100] % - - def _onError(self, package_id: str) -> None: - self._progress.pop(package_id) - self._error.append(package_id) - self._checkDone() - - def _checkDone(self) -> bool: - for item in self._progress.values(): - if not item["file_written"]: - return False - - success_items = { - package_id: - { - "package_path": value["file_written"], - "icon_url": value["package_model"]["icon_url"] - } - for package_id, value in self._progress.items() - } - error_items = [package_id for package_id in self._error] - - self._progress_message.hide() - self.done.emit(success_items, error_items) - return True diff --git a/plugins/Toolbox/src/CloudSync/LicenseModel.py b/plugins/Toolbox/src/CloudSync/LicenseModel.py deleted file mode 100644 index 335a91ef84..0000000000 --- a/plugins/Toolbox/src/CloudSync/LicenseModel.py +++ /dev/null @@ -1,77 +0,0 @@ -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal -from UM.i18n import i18nCatalog - -catalog = i18nCatalog("cura") - - -# Model for the ToolboxLicenseDialog -class LicenseModel(QObject): - DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline") - ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree") - - dialogTitleChanged = pyqtSignal() - packageNameChanged = pyqtSignal() - licenseTextChanged = pyqtSignal() - iconChanged = pyqtSignal() - - def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None: - super().__init__() - - self._current_page_idx = 0 - self._page_count = 1 - self._dialogTitle = "" - self._license_text = "" - self._package_name = "" - self._icon_url = "" - self._decline_button_text = decline_button_text - - @pyqtProperty(str, constant = True) - def acceptButtonText(self): - return self.ACCEPT_BUTTON_TEXT - - @pyqtProperty(str, constant = True) - def declineButtonText(self): - return self._decline_button_text - - @pyqtProperty(str, notify=dialogTitleChanged) - def dialogTitle(self) -> str: - return self._dialogTitle - - @pyqtProperty(str, notify=packageNameChanged) - def packageName(self) -> str: - return self._package_name - - def setPackageName(self, name: str) -> None: - self._package_name = name - self.packageNameChanged.emit() - - @pyqtProperty(str, notify=iconChanged) - def iconUrl(self) -> str: - return self._icon_url - - def setIconUrl(self, url: str): - self._icon_url = url - self.iconChanged.emit() - - @pyqtProperty(str, notify=licenseTextChanged) - def licenseText(self) -> str: - return self._license_text - - def setLicenseText(self, license_text: str) -> None: - if self._license_text != license_text: - self._license_text = license_text - self.licenseTextChanged.emit() - - def setCurrentPageIdx(self, idx: int) -> None: - self._current_page_idx = idx - self._updateDialogTitle() - - def setPageCount(self, count: int) -> None: - self._page_count = count - self._updateDialogTitle() - - def _updateDialogTitle(self): - self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement") - if self._page_count > 1: - self._dialogTitle = self._dialogTitle + " ({}/{})".format(self._current_page_idx + 1, self._page_count) - self.dialogTitleChanged.emit() diff --git a/plugins/Toolbox/src/CloudSync/LicensePresenter.py b/plugins/Toolbox/src/CloudSync/LicensePresenter.py deleted file mode 100644 index 39ce11c8d3..0000000000 --- a/plugins/Toolbox/src/CloudSync/LicensePresenter.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import os -from collections import OrderedDict -from typing import Dict, Optional, List, Any - -from PyQt5.QtCore import QObject, pyqtSlot - -from UM.Logger import Logger -from UM.PackageManager import PackageManager -from UM.Signal import Signal -from cura.CuraApplication import CuraApplication -from UM.i18n import i18nCatalog - -from .LicenseModel import LicenseModel - - -class LicensePresenter(QObject): - """Presents licenses for a set of packages for the user to accept or reject. - - Call present() exactly once to show a licenseDialog for a set of packages - Before presenting another set of licenses, create a new instance using resetCopy(). - - licenseAnswers emits a list of Dicts containing answers when the user has made a choice for all provided packages. - """ - - def __init__(self, app: CuraApplication) -> None: - super().__init__() - self._presented = False - """Whether present() has been called and state is expected to be initialized""" - self._catalog = i18nCatalog("cura") - self._dialog = None # type: Optional[QObject] - self._package_manager = app.getPackageManager() # type: PackageManager - # Emits List[Dict[str, [Any]] containing for example - # [{ "package_id": "BarbarianPlugin", "package_path" : "/tmp/dg345as", "accepted" : True }] - self.licenseAnswers = Signal() - - self._current_package_idx = 0 - self._package_models = [] # type: List[Dict] - decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account") - self._license_model = LicenseModel(decline_button_text=decline_button_text) # type: LicenseModel - self._page_count = 0 - - self._app = app - - self._compatibility_dialog_path = "resources/qml/dialogs/ToolboxLicenseDialog.qml" - - def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None: - """Show a license dialog for multiple packages where users can read a license and accept or decline them - - :param plugin_path: Root directory of the Toolbox plugin - :param packages: Dict[package id, file path] - """ - if self._presented: - Logger.error("{clazz} is single-use. Create a new {clazz} instead", clazz=self.__class__.__name__) - return - - path = os.path.join(plugin_path, self._compatibility_dialog_path) - - self._initState(packages) - - if self._page_count == 0: - self.licenseAnswers.emit(self._package_models) - return - - if self._dialog is None: - - context_properties = { - "catalog": self._catalog, - "licenseModel": self._license_model, - "handler": self - } - self._dialog = self._app.createQmlComponent(path, context_properties) - self._presentCurrentPackage() - self._presented = True - - def resetCopy(self) -> "LicensePresenter": - """Clean up and return a new copy with the same settings such as app""" - if self._dialog: - self._dialog.close() - self.licenseAnswers.disconnectAll() - return LicensePresenter(self._app) - - @pyqtSlot() - def onLicenseAccepted(self) -> None: - self._package_models[self._current_package_idx]["accepted"] = True - self._checkNextPage() - - @pyqtSlot() - def onLicenseDeclined(self) -> None: - self._package_models[self._current_package_idx]["accepted"] = False - self._checkNextPage() - - def _initState(self, packages: Dict[str, Dict[str, Any]]) -> None: - - implicitly_accepted_count = 0 - - for package_id, item in packages.items(): - item["package_id"] = package_id - try: - item["licence_content"] = self._package_manager.getPackageLicense(item["package_path"]) - except EnvironmentError as e: - Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}") - continue # Skip this package. - if item["licence_content"] is None: - # Implicitly accept when there is no license - item["accepted"] = True - implicitly_accepted_count = implicitly_accepted_count + 1 - self._package_models.append(item) - else: - item["accepted"] = None #: None: no answer yet - # When presenting the packages, we want to show packages which have a license first. - # In fact, we don't want to show the others at all because they are implicitly accepted - self._package_models.insert(0, item) - CuraApplication.getInstance().processEvents() - self._page_count = len(self._package_models) - implicitly_accepted_count - self._license_model.setPageCount(self._page_count) - - - def _presentCurrentPackage(self) -> None: - package_model = self._package_models[self._current_package_idx] - package_info = self._package_manager.getPackageInfo(package_model["package_path"]) - - self._license_model.setCurrentPageIdx(self._current_package_idx) - self._license_model.setPackageName(package_info["display_name"]) - self._license_model.setIconUrl(package_model["icon_url"]) - self._license_model.setLicenseText(package_model["licence_content"]) - if self._dialog: - self._dialog.open() # Does nothing if already open - - def _checkNextPage(self) -> None: - if self._current_package_idx + 1 < self._page_count: - self._current_package_idx += 1 - self._presentCurrentPackage() - else: - if self._dialog: - self._dialog.close() - self.licenseAnswers.emit(self._package_models) - - - diff --git a/plugins/Toolbox/src/CloudSync/RestartApplicationPresenter.py b/plugins/Toolbox/src/CloudSync/RestartApplicationPresenter.py deleted file mode 100644 index 8776d1782a..0000000000 --- a/plugins/Toolbox/src/CloudSync/RestartApplicationPresenter.py +++ /dev/null @@ -1,32 +0,0 @@ -from UM import i18nCatalog -from UM.Message import Message -from cura.CuraApplication import CuraApplication - - -class RestartApplicationPresenter: - """Presents a dialog telling the user that a restart is required to apply changes - - Since we cannot restart Cura, the app is closed instead when the button is clicked - """ - def __init__(self, app: CuraApplication) -> None: - self._app = app - self._i18n_catalog = i18nCatalog("cura") - - def present(self) -> None: - app_name = self._app.getApplicationDisplayName() - - message = Message(self._i18n_catalog.i18nc("@info:generic", - "You need to quit and restart {} before changes have effect.", - app_name)) - - message.addAction("quit", - name="Quit " + app_name, - icon = "", - description="Close the application", - button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) - - message.actionTriggered.connect(self._quitClicked) - message.show() - - def _quitClicked(self, *_): - self._app.windowClosed() diff --git a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py b/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py deleted file mode 100644 index db16c5ea84..0000000000 --- a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2020 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtCore import Qt, pyqtProperty, pyqtSlot - -from UM.PackageManager import PackageManager -from UM.Qt.ListModel import ListModel -from UM.Version import Version - -from cura import ApplicationMetadata -from typing import List, Dict, Any - - -class SubscribedPackagesModel(ListModel): - def __init__(self, parent = None): - super().__init__(parent) - - self._items = [] - self._metadata = None - self._discrepancies = None - self._sdk_version = ApplicationMetadata.CuraSDKVersion - - self.addRoleName(Qt.UserRole + 1, "package_id") - self.addRoleName(Qt.UserRole + 2, "display_name") - self.addRoleName(Qt.UserRole + 3, "icon_url") - self.addRoleName(Qt.UserRole + 4, "is_compatible") - self.addRoleName(Qt.UserRole + 5, "is_dismissed") - - @pyqtProperty(bool, constant=True) - def hasCompatiblePackages(self) -> bool: - for item in self._items: - if item['is_compatible']: - return True - return False - - @pyqtProperty(bool, constant=True) - def hasIncompatiblePackages(self) -> bool: - for item in self._items: - if not item['is_compatible']: - return True - return False - - def addDiscrepancies(self, discrepancy: List[str]) -> None: - self._discrepancies = discrepancy - - def getCompatiblePackages(self) -> List[Dict[str, Any]]: - return [package for package in self._items if package["is_compatible"]] - - def getIncompatiblePackages(self) -> List[str]: - return [package["package_id"] for package in self._items if not package["is_compatible"]] - - def initialize(self, package_manager: PackageManager, subscribed_packages_payload: List[Dict[str, Any]]) -> None: - self._items.clear() - for item in subscribed_packages_payload: - if item["package_id"] not in self._discrepancies: - continue - package = { - "package_id": item["package_id"], - "display_name": item["display_name"], - "sdk_versions": item["sdk_versions"], - "download_url": item["download_url"], - "md5_hash": item["md5_hash"], - "is_dismissed": False, - } - - compatible = any(package_manager.isPackageCompatible(Version(version)) for version in item["sdk_versions"]) - package.update({"is_compatible": compatible}) - - try: - package.update({"icon_url": item["icon_url"]}) - except KeyError: # There is no 'icon_url" in the response payload for this package - package.update({"icon_url": ""}) - self._items.append(package) - self.setItems(self._items) diff --git a/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py b/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py deleted file mode 100644 index bb37c6d4a9..0000000000 --- a/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import os -from typing import List, Dict, Any, cast - -from UM import i18n_catalog -from UM.Extension import Extension -from UM.Logger import Logger -from UM.Message import Message -from UM.PluginRegistry import PluginRegistry -from cura.CuraApplication import CuraApplication -from .CloudPackageChecker import CloudPackageChecker -from .CloudApiClient import CloudApiClient -from .DiscrepanciesPresenter import DiscrepanciesPresenter -from .DownloadPresenter import DownloadPresenter -from .LicensePresenter import LicensePresenter -from .RestartApplicationPresenter import RestartApplicationPresenter -from .SubscribedPackagesModel import SubscribedPackagesModel - - -class SyncOrchestrator(Extension): - """Orchestrates the synchronizing of packages from the user account to the installed packages - - Example flow: - - - CloudPackageChecker compares a list of packages the user `subscribed` to in their account - If there are `discrepancies` between the account and locally installed packages, they are emitted - - DiscrepanciesPresenter shows a list of packages to be added or removed to the user. It emits the `packageMutations` - the user selected to be performed - - The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed - - The DownloadPresenter shows a download progress dialog. It emits A tuple of succeeded and failed downloads - - The LicensePresenter extracts licenses from the downloaded packages and presents a license for each package to - be installed. It emits the `licenseAnswers` signal for accept or declines - - The CloudApiClient removes the declined packages from the account - - The SyncOrchestrator uses PackageManager to install the downloaded packages and delete temp files. - - The RestartApplicationPresenter notifies the user that a restart is required for changes to take effect - """ - - def __init__(self, app: CuraApplication) -> None: - super().__init__() - # Differentiate This PluginObject from the Toolbox. self.getId() includes _name. - # getPluginId() will return the same value for The toolbox extension and this one - self._name = "SyncOrchestrator" - - self._package_manager = app.getPackageManager() - # Keep a reference to the CloudApiClient. it watches for installed packages and subscribes to them - self._cloud_api = CloudApiClient.getInstance(app) # type: CloudApiClient - - self._checker = CloudPackageChecker(app) # type: CloudPackageChecker - self._checker.discrepancies.connect(self._onDiscrepancies) - - self._discrepancies_presenter = DiscrepanciesPresenter(app) # type: DiscrepanciesPresenter - self._discrepancies_presenter.packageMutations.connect(self._onPackageMutations) - - self._download_presenter = DownloadPresenter(app) # type: DownloadPresenter - - self._license_presenter = LicensePresenter(app) # type: LicensePresenter - self._license_presenter.licenseAnswers.connect(self._onLicenseAnswers) - - self._restart_presenter = RestartApplicationPresenter(app) - - def _onDiscrepancies(self, model: SubscribedPackagesModel) -> None: - plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId())) - self._discrepancies_presenter.present(plugin_path, model) - - def _onPackageMutations(self, mutations: SubscribedPackagesModel) -> None: - self._download_presenter = self._download_presenter.resetCopy() - self._download_presenter.done.connect(self._onDownloadFinished) - self._download_presenter.download(mutations) - - def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None: - """Called when a set of packages have finished downloading - - :param success_items:: Dict[package_id, Dict[str, str]] - :param error_items:: List[package_id] - """ - if error_items: - message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items))) - self._showErrorMessage(message) - - plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId())) - self._license_presenter = self._license_presenter.resetCopy() - self._license_presenter.licenseAnswers.connect(self._onLicenseAnswers) - self._license_presenter.present(plugin_path, success_items) - - # Called when user has accepted / declined all licenses for the downloaded packages - def _onLicenseAnswers(self, answers: List[Dict[str, Any]]) -> None: - has_changes = False # True when at least one package is installed - - for item in answers: - if item["accepted"]: - # install and subscribe packages - if not self._package_manager.installPackage(item["package_path"]): - message = "Could not install {}".format(item["package_id"]) - self._showErrorMessage(message) - continue - has_changes = True - else: - self._cloud_api.unsubscribe(item["package_id"]) - # delete temp file - try: - os.remove(item["package_path"]) - except EnvironmentError as e: # File was already removed, no access rights, etc. - Logger.error("Can't delete temporary package file: {err}".format(err = str(e))) - - if has_changes: - self._restart_presenter.present() - - def _showErrorMessage(self, text: str): - """Logs an error and shows it to the user""" - - Logger.error(text) - Message(text, lifetime = 0, message_type = Message.MessageType.ERROR).show() diff --git a/plugins/Toolbox/src/CloudSync/__init__.py b/plugins/Toolbox/src/CloudSync/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/Toolbox/src/ConfigsModel.py b/plugins/Toolbox/src/ConfigsModel.py deleted file mode 100644 index a53817653f..0000000000 --- a/plugins/Toolbox/src/ConfigsModel.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtCore import Qt - -from UM.Qt.ListModel import ListModel - - -class ConfigsModel(ListModel): - """Model that holds supported configurations (for material/quality packages).""" - - def __init__(self, parent = None): - super().__init__(parent) - - self._configs = None - - self.addRoleName(Qt.UserRole + 1, "machine") - self.addRoleName(Qt.UserRole + 2, "print_core") - self.addRoleName(Qt.UserRole + 3, "build_plate") - self.addRoleName(Qt.UserRole + 4, "support_material") - self.addRoleName(Qt.UserRole + 5, "quality") - - def setConfigs(self, configs): - self._configs = configs - self._update() - - def _update(self): - items = [] - for item in self._configs: - items.append({ - "machine": item["machine"], - "print_core": item["print_core"], - "build_plate": item["build_plate"], - "support_material": item["support_material"], - "quality": item["quality"] - }) - - self.setItems(items) diff --git a/plugins/Toolbox/src/PackagesModel.py b/plugins/Toolbox/src/PackagesModel.py deleted file mode 100644 index 97645ae466..0000000000 --- a/plugins/Toolbox/src/PackagesModel.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import re -from typing import Dict - -from PyQt5.QtCore import Qt, pyqtProperty - -from UM.Logger import Logger -from UM.Qt.ListModel import ListModel - -from .ConfigsModel import ConfigsModel - - -class PackagesModel(ListModel): - """Model that holds Cura packages. - - By setting the filter property the instances held by this model can be changed. - """ - - def __init__(self, parent = None): - super().__init__(parent) - - self._metadata = None - - 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_id") - self.addRoleName(Qt.UserRole + 6, "author_name") - self.addRoleName(Qt.UserRole + 7, "author_email") - self.addRoleName(Qt.UserRole + 8, "description") - self.addRoleName(Qt.UserRole + 9, "icon_url") - self.addRoleName(Qt.UserRole + 10, "image_urls") - self.addRoleName(Qt.UserRole + 11, "download_url") - self.addRoleName(Qt.UserRole + 12, "last_updated") - self.addRoleName(Qt.UserRole + 13, "is_bundled") - self.addRoleName(Qt.UserRole + 14, "is_active") - self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed - self.addRoleName(Qt.UserRole + 16, "has_configs") - self.addRoleName(Qt.UserRole + 17, "supported_configs") - self.addRoleName(Qt.UserRole + 18, "download_count") - self.addRoleName(Qt.UserRole + 19, "tags") - self.addRoleName(Qt.UserRole + 20, "links") - self.addRoleName(Qt.UserRole + 21, "website") - self.addRoleName(Qt.UserRole + 22, "login_required") - - # List of filters for queries. The result is the union of the each list of results. - self._filter = {} # type: Dict[str, str] - - def setMetadata(self, data): - if self._metadata != data: - self._metadata = data - self._update() - - def _update(self): - items = [] - - if self._metadata is None: - self.setItems(items) - return - - for package in self._metadata: - has_configs = False - configs_model = None - - links_dict = {} - if "data" in package: - # Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier - # to process. - link_list = package["data"]["links"] if "links" in package["data"] else [] - links_dict = {d["title"]: d["url"] for d in link_list} - - # This code never gets executed because the API response does not contain "supported_configs" in it - # It is so because 2y ago when this was created - it did contain it. But it was a prototype only - # and never got to production. As agreed with the team, it'll stay here for now, in case we decide to rework and use it - # The response payload has been changed. Please see: - # https://github.com/Ultimaker/Cura/compare/CURA-7072-temp?expand=1 - if "supported_configs" in package["data"]: - if len(package["data"]["supported_configs"]) > 0: - has_configs = True - configs_model = ConfigsModel() - configs_model.setConfigs(package["data"]["supported_configs"]) - - if "author_id" not in package["author"] or "display_name" not in package["author"]: - package["author"]["author_id"] = "" - package["author"]["display_name"] = "" - - items.append({ - "id": package["package_id"], - "type": package["package_type"], - "name": package["display_name"].strip(), - "version": package["package_version"], - "author_id": package["author"]["author_id"], - "author_name": package["author"]["display_name"], - "author_email": package["author"]["email"] if "email" in package["author"] else None, - "description": package["description"] if "description" in package else None, - "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, - "is_bundled": package["is_bundled"] if "is_bundled" in package else False, - "is_active": package["is_active"] if "is_active" in package else False, - "is_installed": package["is_installed"] if "is_installed" in package else False, - "has_configs": has_configs, - "supported_configs": configs_model, - "download_count": package["download_count"] if "download_count" in package else 0, - "tags": package["tags"] if "tags" in package else [], - "links": links_dict, - "website": package["website"] if "website" in package else None, - "login_required": "login-required" in package.get("tags", []), - }) - - # Filter on all the key-word arguments. - for key, value in self._filter.items(): - if key == "tags": - key_filter = lambda item, v = value: v in item["tags"] - elif "*" in value: - key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v) - else: - key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v) - items = filter(key_filter, items) - - # Execute all filters. - filtered_items = list(items) - - filtered_items.sort(key = lambda k: k["name"]) - self.setItems(filtered_items) - - def setFilter(self, filter_dict: Dict[str, str]) -> None: - """Set the filter of this model based on a string. - - :param filter_dict: Dictionary to do the filtering by. - """ - if filter_dict != self._filter: - self._filter = filter_dict - self._update() - - @pyqtProperty("QVariantMap", fset = setFilter, constant = True) - def filter(self) -> Dict[str, str]: - return self._filter - - # Check to see if a container matches with a regular expression - def _matchRegExp(self, metadata, property_name, value): - if property_name not in metadata: - return False - value = re.escape(value) #Escape for regex patterns. - value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match. - if self._ignore_case: - value_pattern = re.compile(value, re.IGNORECASE) - else: - value_pattern = re.compile(value) - - return value_pattern.match(str(metadata[property_name])) - - # Check to see if a container matches with a string - def _matchString(self, metadata, property_name, value): - if property_name not in metadata: - return False - return value.lower() == str(metadata[property_name]).lower() diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py deleted file mode 100644 index e300d0ff34..0000000000 --- a/plugins/Toolbox/src/Toolbox.py +++ /dev/null @@ -1,878 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import json -import os -import tempfile -from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union - -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply - -from UM.Extension import Extension -from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry -from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope -from UM.Version import Version -from UM.i18n import i18nCatalog -from cura import ApplicationMetadata -from cura.CuraApplication import CuraApplication -from cura.Machines.ContainerTree import ContainerTree -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope -from .AuthorsModel import AuthorsModel -from .CloudApiModel import CloudApiModel -from .CloudSync.LicenseModel import LicenseModel -from .PackagesModel import PackagesModel - -if TYPE_CHECKING: - from UM.TaskManagement.HttpRequestData import HttpRequestData - from cura.Settings.GlobalStack import GlobalStack - -i18n_catalog = i18nCatalog("cura") - -DEFAULT_MARKETPLACE_ROOT = "https://marketplace.ultimaker.com" # type: str - -try: - from cura.CuraVersion import CuraMarketplaceRoot -except ImportError: - CuraMarketplaceRoot = DEFAULT_MARKETPLACE_ROOT - - -class Toolbox(QObject, Extension): - """Provides a marketplace for users to download plugins an materials""" - - def __init__(self, application: CuraApplication) -> None: - super().__init__() - - self._application = application # type: CuraApplication - - # Network: - self._download_request_data = None # type: Optional[HttpRequestData] - self._download_progress = 0 # type: float - self._is_downloading = False # type: bool - self._cloud_scope = UltimakerCloudScope(application) # type: UltimakerCloudScope - self._json_scope = JsonDecoratorScope(self._cloud_scope) # type: JsonDecoratorScope - - self._request_urls = {} # type: Dict[str, str] - self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated - self._old_plugin_ids = set() # type: Set[str] - self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] - - # The responses as given by the server parsed to a list. - self._server_response_data = { - "authors": [], - "packages": [], - "updates": [] - } # type: Dict[str, List[Any]] - - # Models: - self._models = { - "authors": AuthorsModel(self), - "packages": PackagesModel(self), - "updates": PackagesModel(self) - } # type: Dict[str, Union[AuthorsModel, PackagesModel]] - - self._plugins_showcase_model = PackagesModel(self) - self._plugins_available_model = PackagesModel(self) - self._plugins_installed_model = PackagesModel(self) - self._plugins_installed_model.setFilter({"is_bundled": "False"}) - self._plugins_bundled_model = PackagesModel(self) - self._plugins_bundled_model.setFilter({"is_bundled": "True"}) - self._materials_showcase_model = AuthorsModel(self) - self._materials_available_model = AuthorsModel(self) - self._materials_installed_model = PackagesModel(self) - self._materials_installed_model.setFilter({"is_bundled": "False"}) - self._materials_bundled_model = PackagesModel(self) - self._materials_bundled_model.setFilter({"is_bundled": "True"}) - self._materials_generic_model = PackagesModel(self) - - self._license_model = LicenseModel() - - # These properties are for keeping track of the UI state: - # ---------------------------------------------------------------------- - # View category defines which filter to use, and therefore effectively - # which category is currently being displayed. For example, possible - # values include "plugin" or "material", but also "installed". - self._view_category = "plugin" # type: str - - # View page defines which type of page layout to use. For example, - # possible values include "overview", "detail" or "author". - self._view_page = "welcome" # type: str - - # Active package refers to which package is currently being downloaded, - # installed, or otherwise modified. - self._active_package = None # type: Optional[Dict[str, Any]] - - self._dialog = None # type: Optional[QObject] - self._confirm_reset_dialog = None # type: Optional[QObject] - self._resetUninstallVariables() - - self._restart_required = False # type: bool - - # variables for the license agreement dialog - self._license_dialog_plugin_file_location = "" # type: str - - self._application.initializationFinished.connect(self._onAppInitialized) - - # Signals: - # -------------------------------------------------------------------------- - # Downloading changes - activePackageChanged = pyqtSignal() - onDownloadProgressChanged = pyqtSignal() - onIsDownloadingChanged = pyqtSignal() - restartRequiredChanged = pyqtSignal() - installChanged = pyqtSignal() - toolboxEnabledChanged = pyqtSignal() - - # UI changes - viewChanged = pyqtSignal() - detailViewChanged = pyqtSignal() - filterChanged = pyqtSignal() - metadataChanged = pyqtSignal() - showLicenseDialog = pyqtSignal() - closeLicenseDialog = pyqtSignal() - uninstallVariablesChanged = pyqtSignal() - - def _restart(self): - """Go back to the start state (welcome screen or loading if no login required)""" - - # For an Essentials build, login is mandatory - if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion: - self.setViewPage("welcome") - else: - self.setViewPage("loading") - self._fetchPackageData() - - def _resetUninstallVariables(self) -> None: - self._package_id_to_uninstall = None # type: Optional[str] - self._package_name_to_uninstall = "" - self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]] - self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]] - - def getLicenseDialogPluginFileLocation(self) -> str: - return self._license_dialog_plugin_file_location - - def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None: - # Set page 1/1 when opening the dialog for a single package - self._license_model.setCurrentPageIdx(0) - self._license_model.setPageCount(1) - self._license_model.setIconUrl(icon_url) - - self._license_model.setPackageName(plugin_name) - self._license_model.setLicenseText(license_content) - self._license_dialog_plugin_file_location = plugin_file_location - self.showLicenseDialog.emit() - - # This is a plugin, so most of the components required are not ready when - # this is initialized. Therefore, we wait until the application is ready. - def _onAppInitialized(self) -> None: - self._plugin_registry = self._application.getPluginRegistry() - self._package_manager = self._application.getPackageManager() - - # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc. - installed_package_ids_with_versions = [":".join(items) for items in - self._package_manager.getAllInstalledPackageIdsAndVersions()] - installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions) - - self._request_urls = { - "authors": "{base_url}/authors".format(base_url = CloudApiModel.api_url), - "packages": "{base_url}/packages".format(base_url = CloudApiModel.api_url), - "updates": "{base_url}/packages/package-updates?installed_packages={query}".format( - base_url = CloudApiModel.api_url, query = installed_packages_query) - } - - self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) - - preferences = CuraApplication.getInstance().getPreferences() - - preferences.addPreference("info/automatic_plugin_update_check", True) - - # On boot we check which packages have updates. - if preferences.getValue("info/automatic_plugin_update_check") and len(installed_package_ids_with_versions) > 0: - # Request the latest and greatest! - self._makeRequestByType("updates") - - def _fetchPackageData(self) -> None: - self._makeRequestByType("packages") - self._makeRequestByType("authors") - self._updateInstalledModels() - - # Displays the toolbox - @pyqtSlot() - def launch(self) -> None: - if not self._dialog: - self._dialog = self._createDialog("Toolbox.qml") - - if not self._dialog: - Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.") - return - - self._restart() - - self._dialog.show() - # Apply enabled/disabled state to installed plugins - self.toolboxEnabledChanged.emit() - - def _createDialog(self, qml_name: str) -> Optional[QObject]: - Logger.log("d", "Marketplace: Creating dialog [%s].", qml_name) - plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) - if not plugin_path: - return None - path = os.path.join(plugin_path, "resources", "qml", qml_name) - - dialog = self._application.createQmlComponent(path, { - "toolbox": self, - "handler": self, - "licenseModel": self._license_model - }) - if not dialog: - return None - return dialog - - def _convertPluginMetadata(self, plugin_data: Dict[str, Any]) -> Optional[Dict[str, Any]]: - try: - highest_sdk_version_supported = Version(0) - for supported_version in plugin_data["plugin"]["supported_sdk_versions"]: - if supported_version > highest_sdk_version_supported: - highest_sdk_version_supported = supported_version - - formatted = { - "package_id": plugin_data["id"], - "package_type": "plugin", - "display_name": plugin_data["plugin"]["name"], - "package_version": plugin_data["plugin"]["version"], - "sdk_version": highest_sdk_version_supported, - "author": { - "author_id": plugin_data["plugin"]["author"], - "display_name": plugin_data["plugin"]["author"] - }, - "is_installed": True, - "description": plugin_data["plugin"]["description"] - } - return formatted - except KeyError: - Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data)) - return None - - @pyqtSlot() - def _updateInstalledModels(self) -> None: - # This is moved here to avoid code duplication and so that after installing plugins they get removed from the - # list of old plugins - old_plugin_ids = self._plugin_registry.getInstalledPlugins() - installed_package_ids = self._package_manager.getAllInstalledPackageIDs() - scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs() - - self._old_plugin_ids = set() - self._old_plugin_metadata = dict() - - for plugin_id in old_plugin_ids: - # Neither the installed packages nor the packages that are scheduled to remove are old plugins - if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids: - Logger.log("d", "Found a plugin that was installed with the old plugin browser: %s", plugin_id) - - old_metadata = self._plugin_registry.getMetaData(plugin_id) - new_metadata = self._convertPluginMetadata(old_metadata) - if new_metadata is None: - # Something went wrong converting it. - continue - self._old_plugin_ids.add(plugin_id) - self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata - - all_packages = self._package_manager.getAllInstalledPackagesInfo() - if "plugin" in all_packages: - # For old plugins, we only want to include the old custom plugin that were installed via the old toolbox. - # The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled - # plugins should be excluded from the old plugins list/dict. - all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"]) - self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_ids - if plugin_id not in all_plugin_package_ids) - self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids} - - self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values())) - self._plugins_bundled_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values())) - self.metadataChanged.emit() - if "material" in all_packages: - self._materials_installed_model.setMetadata(all_packages["material"]) - self._materials_bundled_model.setMetadata(all_packages["material"]) - self.metadataChanged.emit() - - @pyqtSlot(str) - def install(self, file_path: str) -> Optional[str]: - package_id = self._package_manager.installPackage(file_path) - self.installChanged.emit() - self._updateInstalledModels() - self.metadataChanged.emit() - self._restart_required = True - self.restartRequiredChanged.emit() - return package_id - - @pyqtSlot(str) - def checkPackageUsageAndUninstall(self, package_id: str) -> None: - """Check package usage and uninstall - - If the package is in use, you'll get a confirmation dialog to set everything to default - """ - - package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id) - if package_used_materials or package_used_qualities: - # Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall - self._package_id_to_uninstall = package_id - package_info = self._package_manager.getInstalledPackageInfo(package_id) - self._package_name_to_uninstall = package_info.get("display_name", package_info.get("package_id")) - self._package_used_materials = package_used_materials - self._package_used_qualities = package_used_qualities - # Ask change to default material / profile - if self._confirm_reset_dialog is None: - self._confirm_reset_dialog = self._createDialog("dialogs/ToolboxConfirmUninstallResetDialog.qml") - self.uninstallVariablesChanged.emit() - if self._confirm_reset_dialog is None: - Logger.log("e", "ToolboxConfirmUninstallResetDialog should have been initialized, but it is not. Not showing dialog and not uninstalling package.") - else: - self._confirm_reset_dialog.show() - else: - # Plain uninstall - self.uninstall(package_id) - - @pyqtProperty(str, notify = uninstallVariablesChanged) - def pluginToUninstall(self) -> str: - return self._package_name_to_uninstall - - @pyqtProperty(str, notify = uninstallVariablesChanged) - def uninstallUsedMaterials(self) -> str: - return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials]) - - @pyqtProperty(str, notify = uninstallVariablesChanged) - def uninstallUsedQualities(self) -> str: - return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities]) - - @pyqtSlot() - def closeConfirmResetDialog(self) -> None: - if self._confirm_reset_dialog is not None: - self._confirm_reset_dialog.close() - - @pyqtSlot() - def resetMaterialsQualitiesAndUninstall(self) -> None: - """Uses "uninstall variables" to reset qualities and materials, then uninstall - - It's used as an action on Confirm reset on Uninstall - """ - - application = CuraApplication.getInstance() - machine_manager = application.getMachineManager() - container_tree = ContainerTree.getInstance() - - for global_stack, extruder_nr, container_id in self._package_used_materials: - extruder = global_stack.extruderList[int(extruder_nr)] - approximate_diameter = extruder.getApproximateMaterialDiameter() - variant_node = container_tree.machines[global_stack.definition.getId()].variants[extruder.variant.getName()] - default_material_node = variant_node.preferredMaterial(approximate_diameter) - machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack) - for global_stack, extruder_nr, container_id in self._package_used_qualities: - variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList] - material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList] - extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList] - definition_id = global_stack.definition.getId() - machine_node = container_tree.machines[definition_id] - default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type] - machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack) - - if self._package_id_to_uninstall is not None: - self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall) - self.uninstall(self._package_id_to_uninstall) - self._resetUninstallVariables() - self.closeConfirmResetDialog() - - @pyqtSlot() - def onLicenseAccepted(self): - self.closeLicenseDialog.emit() - package_id = self.install(self.getLicenseDialogPluginFileLocation()) - - - @pyqtSlot() - def onLicenseDeclined(self): - self.closeLicenseDialog.emit() - - def _markPackageMaterialsAsToBeUninstalled(self, package_id: str) -> None: - container_registry = self._application.getContainerRegistry() - - all_containers = self._package_manager.getPackageContainerIds(package_id) - for container_id in all_containers: - containers = container_registry.findInstanceContainers(id = container_id) - if not containers: - continue - container = containers[0] - if container.getMetaDataEntry("type") != "material": - continue - root_material_id = container.getMetaDataEntry("base_file") - root_material_containers = container_registry.findInstanceContainers(id = root_material_id) - if not root_material_containers: - continue - root_material_container = root_material_containers[0] - root_material_container.setMetaDataEntry("removed", True) - - @pyqtSlot(str) - def uninstall(self, package_id: str) -> None: - self._package_manager.removePackage(package_id, force_add = True) - self.installChanged.emit() - self._updateInstalledModels() - self.metadataChanged.emit() - self._restart_required = True - self.restartRequiredChanged.emit() - - def _update(self) -> None: - """Actual update packages that are in self._to_update""" - - if self._to_update: - plugin_id = self._to_update.pop(0) - remote_package = self.getRemotePackage(plugin_id) - if remote_package: - download_url = remote_package["download_url"] - Logger.log("d", "Updating package [%s]..." % plugin_id) - self.startDownload(download_url) - else: - Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id) - - if self._to_update: - self._application.callLater(self._update) - - @pyqtSlot(str) - def update(self, plugin_id: str) -> None: - """Update a plugin by plugin_id""" - - self._to_update.append(plugin_id) - self._application.callLater(self._update) - - @pyqtSlot(str) - def enable(self, plugin_id: str) -> None: - self._plugin_registry.enablePlugin(plugin_id) - self.toolboxEnabledChanged.emit() - Logger.log("i", "%s was set as 'active'.", plugin_id) - self._restart_required = True - self.restartRequiredChanged.emit() - - @pyqtSlot(str) - def disable(self, plugin_id: str) -> None: - self._plugin_registry.disablePlugin(plugin_id) - self.toolboxEnabledChanged.emit() - Logger.log("i", "%s was set as 'deactive'.", plugin_id) - self._restart_required = True - self.restartRequiredChanged.emit() - - @pyqtProperty(bool, notify = metadataChanged) - def dataReady(self) -> bool: - return self._packages_model is not None - - @pyqtProperty(bool, notify = restartRequiredChanged) - def restartRequired(self) -> bool: - return self._restart_required - - @pyqtSlot() - def restart(self) -> None: - self._application.windowClosed() - - def getRemotePackage(self, package_id: str) -> Optional[Dict]: - # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. - remote_package = None - for package in self._server_response_data["packages"]: - if package["package_id"] == package_id: - remote_package = package - break - return remote_package - - @pyqtSlot(str, result = bool) - def canDowngrade(self, package_id: str) -> bool: - # If the currently installed version is higher than the bundled version (if present), the we can downgrade - # this package. - local_package = self._package_manager.getInstalledPackageInfo(package_id) - if local_package is None: - return False - - bundled_package = self._package_manager.getBundledPackageInfo(package_id) - if bundled_package is None: - return False - - local_version = Version(local_package["package_version"]) - bundled_version = Version(bundled_package["package_version"]) - return bundled_version < local_version - - @pyqtSlot(str, result = bool) - def isInstalled(self, package_id: str) -> bool: - result = self._package_manager.isPackageInstalled(package_id) - # Also check the old plugins list if it's not found in the package manager. - if not result: - result = self.isOldPlugin(package_id) - return result - - @pyqtSlot(str, result = int) - def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int: - count = 0 - for package in self._materials_installed_model.items: - if package["author_id"] == author_id: - count += 1 - return count - - # This slot is only used to get the number of material packages by author, not any other type of packages. - @pyqtSlot(str, result = int) - def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int: - count = 0 - for package in self._server_response_data["packages"]: - if package["package_type"] == "material": - if package["author"]["author_id"] == author_id: - count += 1 - return count - - @pyqtSlot(str, result = bool) - def isEnabled(self, package_id: str) -> bool: - return package_id in self._plugin_registry.getActivePlugins() - - # Check for plugins that were installed with the old plugin browser - def isOldPlugin(self, plugin_id: str) -> bool: - return plugin_id in self._old_plugin_ids - - def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]: - return self._old_plugin_metadata.get(plugin_id) - - def isLoadingComplete(self) -> bool: - populated = 0 - for metadata_list in self._server_response_data.items(): - if metadata_list: - populated += 1 - return populated == len(self._server_response_data.items()) - - # Make API Calls - # -------------------------------------------------------------------------- - def _makeRequestByType(self, request_type: str) -> None: - Logger.debug(f"Requesting {request_type} metadata from server.") - url = self._request_urls[request_type] - - callback = lambda r, rt = request_type: self._onDataRequestFinished(rt, r) - error_callback = lambda r, e, rt = request_type: self._onDataRequestError(rt, r, e) - self._application.getHttpRequestManager().get(url, - callback = callback, - error_callback = error_callback, - scope=self._json_scope) - - @pyqtSlot(str) - def startDownload(self, url: str) -> None: - Logger.info(f"Attempting to download & install package from {url}.") - - callback = lambda r: self._onDownloadFinished(r) - error_callback = lambda r, e: self._onDownloadFailed(r, e) - download_progress_callback = self._onDownloadProgress - request_data = self._application.getHttpRequestManager().get(url, - callback = callback, - error_callback = error_callback, - download_progress_callback = download_progress_callback, - scope=self._cloud_scope - ) - - self._download_request_data = request_data - self.setDownloadProgress(0) - self.setIsDownloading(True) - - @pyqtSlot() - def cancelDownload(self) -> None: - Logger.info(f"User cancelled the download of a package. request {self._download_request_data}") - if self._download_request_data is not None: - self._application.getHttpRequestManager().abortRequest(self._download_request_data) - self._download_request_data = None - self.resetDownload() - - def resetDownload(self) -> None: - self.setDownloadProgress(0) - self.setIsDownloading(False) - - # Handlers for Network Events - # -------------------------------------------------------------------------- - def _onDataRequestError(self, request_type: str, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None: - Logger.error(f"Request {request_type} failed due to error {error}: {reply.errorString()}") - self.setViewPage("errored") - - def _onDataRequestFinished(self, request_type: str, reply: "QNetworkReply") -> None: - if reply.operation() != QNetworkAccessManager.GetOperation: - Logger.log("e", "_onDataRequestFinished() only handles GET requests but got [%s] instead", reply.operation()) - return - - http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) - if http_status_code != 200: - Logger.log("e", "Request type [%s] got non-200 HTTP response: [%s]", http_status_code) - self.setViewPage("errored") - return - - data = bytes(reply.readAll()) - try: - json_data = json.loads(data.decode("utf-8")) - except json.decoder.JSONDecodeError: - Logger.log("e", "Failed to decode response data as JSON for request type [%s], response data [%s]", - request_type, data) - self.setViewPage("errored") - return - - # Check for errors: - if "errors" in json_data: - for error in json_data["errors"]: - Logger.log("e", "Request type [%s] got response showing error: %s", error.get("title", "No error title found")) - self.setViewPage("errored") - return - - # Create model and apply metadata: - if not self._models[request_type]: - Logger.log("e", "Could not find the model for request type [%s].", request_type) - self.setViewPage("errored") - return - - self._server_response_data[request_type] = json_data["data"] - self._models[request_type].setMetadata(self._server_response_data[request_type]) - - if request_type == "packages": - self._models[request_type].setFilter({"type": "plugin"}) - self.reBuildMaterialsModels() - self.reBuildPluginsModels() - self._notifyPackageManager() - elif request_type == "authors": - self._models[request_type].setFilter({"package_types": "material"}) - self._models[request_type].setFilter({"tags": "generic"}) - elif request_type == "updates": - # Tell the package manager that there's a new set of updates available. - packages = self._server_response_data[request_type] - self._package_manager.setPackagesWithUpdate({p['package_id'] for p in packages}) - - self.metadataChanged.emit() - - if self.isLoadingComplete(): - self.setViewPage("overview") - - # This function goes through all known remote versions of a package and notifies the package manager of this change - def _notifyPackageManager(self): - for package in self._server_response_data["packages"]: - self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"])) - - def _onDownloadFinished(self, reply: "QNetworkReply") -> None: - self.resetDownload() - - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: - try: - reply_error = json.loads(reply.readAll().data().decode("utf-8")) - except Exception as e: - reply_error = str(e) - Logger.log("w", "Failed to download package. The following error was returned: %s", reply_error) - return - # Must not delete the temporary file on Windows - self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False) - file_path = self._temp_plugin_file.name - # Write first and close, otherwise on Windows, it cannot read the file - self._temp_plugin_file.write(reply.readAll()) - self._temp_plugin_file.close() - self._onDownloadComplete(file_path) - - def _onDownloadFailed(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None: - Logger.log("w", "Failed to download package. The following error was returned: %s", error) - - self.resetDownload() - - def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None: - if bytes_total > 0: - new_progress = bytes_sent / bytes_total * 100 - self.setDownloadProgress(new_progress) - Logger.log("d", "new download progress %s / %s : %s%%", bytes_sent, bytes_total, new_progress) - - def _onDownloadComplete(self, file_path: str) -> None: - Logger.log("i", "Download complete.") - package_info = self._package_manager.getPackageInfo(file_path) - if not package_info: - Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path) - return - package_id = package_info["package_id"] - - try: - license_content = self._package_manager.getPackageLicense(file_path) - except EnvironmentError as e: - Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}") - return - if license_content is not None: - # get the icon url for package_id, make sure the result is a string, never None - icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or "" - self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url) - return - - installed_id = self.install(file_path) - if installed_id != package_id: - Logger.error("Installed package {} does not match {}".format(installed_id, package_id)) - - # Getter & Setters for Properties: - # -------------------------------------------------------------------------- - def setDownloadProgress(self, progress: float) -> None: - if progress != self._download_progress: - self._download_progress = progress - self.onDownloadProgressChanged.emit() - - @pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged) - def downloadProgress(self) -> float: - return self._download_progress - - def setIsDownloading(self, is_downloading: bool) -> None: - if self._is_downloading != is_downloading: - self._is_downloading = is_downloading - self.onIsDownloadingChanged.emit() - - @pyqtProperty(bool, fset = setIsDownloading, notify = onIsDownloadingChanged) - def isDownloading(self) -> bool: - return self._is_downloading - - def setActivePackage(self, package: QObject) -> None: - if self._active_package != package: - self._active_package = package - self.activePackageChanged.emit() - - @pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged) - def activePackage(self) -> Optional[QObject]: - """The active package is the package that is currently being downloaded""" - - return self._active_package - - def setViewCategory(self, category: str = "plugin") -> None: - if self._view_category != category: - self._view_category = category - self.viewChanged.emit() - - # Function explicitly defined so that it can be called through the callExtensionsMethod - # which cannot receive arguments. - def setViewCategoryToMaterials(self) -> None: - self.setViewCategory("material") - - @pyqtProperty(str, fset = setViewCategory, notify = viewChanged) - def viewCategory(self) -> str: - return self._view_category - - def setViewPage(self, page: str = "overview") -> None: - if self._view_page != page: - self._view_page = page - self.viewChanged.emit() - - @pyqtProperty(str, fset = setViewPage, notify = viewChanged) - def viewPage(self) -> str: - return self._view_page - - # Exposed Models: - # -------------------------------------------------------------------------- - @pyqtProperty(QObject, constant = True) - def authorsModel(self) -> AuthorsModel: - return cast(AuthorsModel, self._models["authors"]) - - @pyqtProperty(QObject, constant = True) - def packagesModel(self) -> PackagesModel: - return cast(PackagesModel, self._models["packages"]) - - @pyqtProperty(QObject, constant = True) - def pluginsShowcaseModel(self) -> PackagesModel: - return self._plugins_showcase_model - - @pyqtProperty(QObject, constant = True) - def pluginsAvailableModel(self) -> PackagesModel: - return self._plugins_available_model - - @pyqtProperty(QObject, constant = True) - def pluginsInstalledModel(self) -> PackagesModel: - return self._plugins_installed_model - - @pyqtProperty(QObject, constant = True) - def pluginsBundledModel(self) -> PackagesModel: - return self._plugins_bundled_model - - @pyqtProperty(QObject, constant = True) - def materialsShowcaseModel(self) -> AuthorsModel: - return self._materials_showcase_model - - @pyqtProperty(QObject, constant = True) - def materialsAvailableModel(self) -> AuthorsModel: - return self._materials_available_model - - @pyqtProperty(QObject, constant = True) - def materialsInstalledModel(self) -> PackagesModel: - return self._materials_installed_model - - @pyqtProperty(QObject, constant = True) - def materialsBundledModel(self) -> PackagesModel: - return self._materials_bundled_model - - @pyqtProperty(QObject, constant = True) - def materialsGenericModel(self) -> PackagesModel: - return self._materials_generic_model - - @pyqtSlot(str, result = str) - def getWebMarketplaceUrl(self, page: str) -> str: - root = CuraMarketplaceRoot - if root == "": - root = DEFAULT_MARKETPLACE_ROOT - return root + "/app/cura/" + page - - # Filter Models: - # -------------------------------------------------------------------------- - @pyqtSlot(str, str, str) - def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None: - if not self._models[model_type]: - Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type) - return - self._models[model_type].setFilter({filter_type: parameter}) - self.filterChanged.emit() - - @pyqtSlot(str, "QVariantMap") - def setFilters(self, model_type: str, filter_dict: dict) -> None: - if not self._models[model_type]: - Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type) - return - self._models[model_type].setFilter(filter_dict) - self.filterChanged.emit() - - @pyqtSlot(str) - def removeFilters(self, model_type: str) -> None: - if not self._models[model_type]: - Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type) - return - self._models[model_type].setFilter({}) - self.filterChanged.emit() - - # HACK(S): - # -------------------------------------------------------------------------- - def reBuildMaterialsModels(self) -> None: - materials_showcase_metadata = [] - materials_available_metadata = [] - materials_generic_metadata = [] - - processed_authors = [] # type: List[str] - - for item in self._server_response_data["packages"]: - if item["package_type"] == "material": - - author = item["author"] - if author["author_id"] in processed_authors: - continue - - # Generic materials to be in the same section - if "generic" in item["tags"]: - materials_generic_metadata.append(item) - else: - if "showcase" in item["tags"]: - materials_showcase_metadata.append(author) - else: - materials_available_metadata.append(author) - - processed_authors.append(author["author_id"]) - - self._materials_showcase_model.setMetadata(materials_showcase_metadata) - self._materials_available_model.setMetadata(materials_available_metadata) - self._materials_generic_model.setMetadata(materials_generic_metadata) - - def reBuildPluginsModels(self) -> None: - plugins_showcase_metadata = [] - plugins_available_metadata = [] - - for item in self._server_response_data["packages"]: - if item["package_type"] == "plugin": - if "showcase" in item["tags"]: - plugins_showcase_metadata.append(item) - else: - plugins_available_metadata.append(item) - - self._plugins_showcase_model.setMetadata(plugins_showcase_metadata) - self._plugins_available_model.setMetadata(plugins_available_metadata) diff --git a/plugins/Toolbox/src/__init__.py b/plugins/Toolbox/src/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index a5d0ffb2a3..1cd21a3ab6 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -526,13 +526,13 @@ } } }, - "Toolbox": { + "Marketplace": { "package_info": { - "package_id": "Toolbox", + "package_id": "Marketplace", "package_type": "plugin", - "display_name": "Toolbox", + "display_name": "Marketplace", "description": "Find, manage and install new Cura packages.", - "package_version": "1.0.1", + "package_version": "1.0.0", "sdk_version": "7.8.0", "website": "https://ultimaker.com", "author": { diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index 2924ae518f..b0dabd42cc 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -196,15 +196,7 @@ Item } } - // show the Toolbox - Connections - { - target: Cura.Actions.browsePackages - function onTriggered() - { - curaExtensions.callExtensionMethod("Toolbox", "launch") - } - } + // show the Marketplace Connections { target: Cura.Actions.openMarketplace diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index a47f8e963c..a28d9424b5 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -83,53 +83,6 @@ Item ExclusiveGroup { id: mainWindowHeaderMenuGroup } } - // Shortcut button to quick access the Toolbox - Controls2.Button //TODO: Remove once new Marketplace is completed. - { - text: "Old Marketplace" - height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) - onClicked: Cura.Actions.browsePackages.trigger() - - hoverEnabled: true - - background: Rectangle - { - id: marketplaceButtonBorder - radius: UM.Theme.getSize("action_button_radius").width - color: UM.Theme.getColor("main_window_header_background") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("primary_text") - - Rectangle - { - id: marketplaceButtonFill - anchors.fill: parent - radius: parent.radius - color: UM.Theme.getColor("primary_text") - opacity: parent.hovered ? 0.2 : 0 - Behavior on opacity { NumberAnimation { duration: 100 } } - } - } - - contentItem: Label - { - id: label - text: parent.text - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("primary_text") - width: contentWidth - verticalAlignment: Text.AlignVCenter - renderType: Text.NativeRendering - } - - anchors - { - right: marketplaceButton.left - rightMargin: UM.Theme.getSize("default_margin").width - verticalCenter: parent.verticalCenter - } - } - Controls2.Button { id: marketplaceButton diff --git a/resources/qml/Widgets/ScrollView.qml b/resources/qml/Widgets/ScrollView.qml index 9e7531994c..deaecb5dfb 100644 --- a/resources/qml/Widgets/ScrollView.qml +++ b/resources/qml/Widgets/ScrollView.qml @@ -1,5 +1,5 @@ // Copyright (c) 2020 Ultimaker B.V. -// Toolbox is released under the terms of the LGPLv3 or higher. +// Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 import QtQuick.Controls 2.3 diff --git a/scripts/check_invalid_imports.py b/scripts/check_invalid_imports.py index ba21b9f822..b77a82568d 100644 --- a/scripts/check_invalid_imports.py +++ b/scripts/check_invalid_imports.py @@ -8,7 +8,7 @@ Run this file with the Cura project root as the working directory Checks for invalid imports. When importing from plugins, there will be no problems when running from source, but for some build types the plugins dir is not on the path, so relative imports should be used instead. eg: from ..UltimakerCloudScope import UltimakerCloudScope <-- OK -import plugins.Toolbox.src ... <-- NOT OK +import plugins.Marketplace.src ... <-- NOT OK """ -- cgit v1.2.3 From 1cdeaffab78b1e7ba3a24a642f70e1e42424ca7e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 30 Dec 2021 11:53:42 +0100 Subject: Make new Marktetplace react to 'get more materials'. part of CURA-8588 --- plugins/Marketplace/Marketplace.py | 33 +++++++++++++++++++---- plugins/Marketplace/resources/qml/Marketplace.qml | 3 ++- resources/qml/MainWindow/ApplicationMenu.qml | 6 ++--- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 143469d82e..ea1f94be1d 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. import os.path -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtQml import qmlRegisterType from typing import Optional, TYPE_CHECKING @@ -15,19 +15,33 @@ from .RemotePackageList import RemotePackageList # To register this type with Q from .LocalPackageList import LocalPackageList # To register this type with QML. from .RestartManager import RestartManager # To register this type with QML. -if TYPE_CHECKING: - from PyQt5.QtCore import QObject - class Marketplace(Extension): """ The main managing object for the Marketplace plug-in. """ + class TabManager(QObject): + def __init__(self) -> None: + super().__init__(parent=CuraApplication.getInstance()) + self._tab_shown = 0 + + def getTabShown(self): + return self._tab_shown + + def setTabShown(self, tab_shown): + if tab_shown != self._tab_shown: + self._tab_shown = tab_shown + self.tabShownChanged.emit() + + tabShownChanged = pyqtSignal() + tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged) + def __init__(self) -> None: super().__init__() self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. self._plugin_registry: Optional[PluginRegistry] = None + self._tab_manager = Marketplace.TabManager() qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") @@ -46,8 +60,17 @@ class Marketplace(Extension): if plugin_path is None: plugin_path = os.path.dirname(__file__) path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {}) + self._window = CuraApplication.getInstance().createQmlComponent(path, {"tabManager": self._tab_manager}) if self._window is None: # Still None? Failed to load the QML then. return + self._tab_manager.setTabShown(0) self._window.show() self._window.requestActivate() # Bring window into focus, if it was already open in the background. + + @pyqtSlot() + def setVisibleTabToMaterials(self) -> None: + """ + Set the tab shown to the remote materials one. + Not implemented in a more generic way because it needs the ability to be called with 'callExtensionMethod'. + """ + self._tab_manager.setTabShown(1) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 017a9e3dde..9027a02121 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -25,7 +25,6 @@ Window onVisibleChanged: { - pageSelectionTabBar.currentIndex = 0; //Go back to the initial tab. while(contextStack.depth > 1) { contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? @@ -131,9 +130,11 @@ Window height: UM.Theme.getSize("button_icon").height spacing: 0 background: Rectangle { color: "transparent" } + currentIndex: tabManager.tabShown onCurrentIndexChanged: { + tabManager.tabShown = currentIndex searchBar.text = ""; searchBar.visible = currentItem.hasSearch; content.source = currentItem.sourcePage; diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index b0dabd42cc..497c5e1541 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -212,8 +212,8 @@ Item target: Cura.Actions.marketplaceMaterials function onTriggered() { - curaExtensions.callExtensionMethod("Toolbox", "launch") - curaExtensions.callExtensionMethod("Toolbox", "setViewCategoryToMaterials") + curaExtensions.callExtensionMethod("Marketplace", "show") + curaExtensions.callExtensionMethod("Marketplace", "setVisibleTabToMaterials") } } -} \ No newline at end of file +} -- cgit v1.2.3 From fe30a3c19e64c2b0f351de6d706557dce540d288 Mon Sep 17 00:00:00 2001 From: casper Date: Thu, 30 Dec 2021 12:24:14 +0100 Subject: Add notification badge for the gear icon in the Market Place CURA 8658 --- plugins/Marketplace/resources/qml/Marketplace.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 017a9e3dde..27826c15b0 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -159,6 +159,24 @@ Window { property string sourcePage: "ManagedPackages.qml" property bool hasSearch: false + + Cura.NotificationIcon + { + anchors + { + top: parent.top + right: parent.right + rightMargin: (-0.5 * width) | 0 + topMargin: (-0.5 * height) | 0 + } + visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 + + labelText: + { + const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length + return itemCount > 9 ? "9+" : itemCount + } + } } } -- cgit v1.2.3 From 02c81234d077191b6d7cc8ce013caaf87bfae6f3 Mon Sep 17 00:00:00 2001 From: casper Date: Thu, 30 Dec 2021 13:38:16 +0100 Subject: Remove references to the toolbox plugin from the themes.json Some other components were (incorrectly) using these theme variables. Replaced them by another theme variable with the same value. CURA 8588 --- .../resources/qml/ProjectSummaryCard.qml | 2 +- .../resources/qml/SelectProjectPage.qml | 2 +- resources/themes/cura-dark/theme.json | 4 -- resources/themes/cura-light/styles.qml | 56 ---------------------- resources/themes/cura-light/theme.json | 21 -------- 5 files changed, 2 insertions(+), 83 deletions(-) diff --git a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml index 4374b2f998..ba2abf22a9 100644 --- a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml +++ b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml @@ -44,7 +44,7 @@ Cura.RoundedRectangle { id: projectImage anchors.verticalCenter: parent.verticalCenter - width: UM.Theme.getSize("toolbox_thumbnail_small").width + width: UM.Theme.getSize("card_icon").width height: Math.round(width * 3/4) sourceSize.width: width sourceSize.height: height diff --git a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml index 9ebf264e0f..8732c03c4a 100644 --- a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml @@ -201,7 +201,7 @@ Item LoadMoreProjectsCard { id: loadMoreProjectsCard - height: UM.Theme.getSize("toolbox_thumbnail_small").height + height: UM.Theme.getSize("card_icon").height width: parent.width visible: manager.digitalFactoryProjectModel.count > 0 hasMoreProjectsToLoad: manager.hasMoreProjectsToLoad diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 433c9fff92..a81dcadb5c 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -176,10 +176,6 @@ "quality_slider_available": [255, 255, 255, 255], - "toolbox_header_button_text_active": [255, 255, 255, 255], - "toolbox_header_button_text_inactive": [128, 128, 128, 255], - "toolbox_premium_packages_background": [57, 57, 57, 255], - "monitor_printer_family_tag": [86, 86, 106, 255], "monitor_text_disabled": [102, 102, 102, 255], "monitor_icon_primary": [229, 229, 229, 255], diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml index 1320b54f37..8376ecb44a 100755 --- a/resources/themes/cura-light/styles.qml +++ b/resources/themes/cura-light/styles.qml @@ -602,62 +602,6 @@ QtObject } } - property Component toolbox_action_button: Component - { - ButtonStyle - { - background: Rectangle - { - implicitWidth: UM.Theme.getSize("toolbox_action_button").width - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - color: - { - if (control.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: - { - if (control.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") - } - } - } - property Component monitor_button_style: Component { ButtonStyle diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 8e9db0e9fe..f48621b46a 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -428,9 +428,6 @@ "printer_config_matched": [50, 130, 255, 255], "printer_config_mismatch": [127, 127, 127, 255], - "toolbox_header_button_text_inactive": [0, 0, 0, 255], - "toolbox_premium_packages_background": [240, 240, 240, 255], - "favorites_header_bar": [245, 245, 245, 255], "favorites_header_hover": [245, 245, 245, 255], "favorites_header_text": [31, 36, 39, 255], @@ -644,24 +641,6 @@ "build_plate_selection_size": [15, 5], "objects_menu_button": [0.3, 2.7], - "toolbox_thumbnail_small": [6.0, 6.0], - "toolbox_thumbnail_medium": [8.0, 8.0], - "toolbox_thumbnail_large": [12.0, 10.0], - "toolbox_footer": [1.0, 4.5], - "toolbox_footer_button": [8.0, 2.5], - "toolbox_header_tab": [12.0, 4.0], - "toolbox_detail_header": [1.0, 14.0], - "toolbox_back_column": [6.0, 1.0], - "toolbox_back_button": [6.0, 2.0], - "toolbox_installed_tile": [1.0, 8.0], - "toolbox_property_label": [1.0, 2.0], - "toolbox_heading_label": [1.0, 3.8], - "toolbox_header": [1.0, 4.0], - "toolbox_header_highlight": [0.25, 0.25], - "toolbox_chart_row": [1.0, 2.0], - "toolbox_action_button": [8.0, 2.5], - "toolbox_loader": [2.0, 2.0], - "notification_icon": [1.5, 1.5], "avatar_image": [6.8, 6.8], -- cgit v1.2.3 From c92fcc8e037daf291e35a0fe4d0b9d5bf110f9c2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 10:08:17 +0100 Subject: Simplify the notifaction icon logic CURA-8658 --- plugins/Marketplace/resources/qml/Marketplace.qml | 6 ++---- plugins/PostProcessingPlugin/PostProcessingPlugin.qml | 6 ++---- resources/qml/MainWindow/MainWindowHeader.qml | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 27826c15b0..c04b24cc13 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -164,10 +164,8 @@ Window { anchors { - top: parent.top - right: parent.right - rightMargin: (-0.5 * width) | 0 - topMargin: (-0.5 * height) | 0 + horizontalCenter: parent.right + verticalCenter: parent.top } visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml index bbba2e7621..bd94d1fdfd 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml @@ -527,10 +527,8 @@ UM.Dialog visible: activeScriptsList.count > 0 anchors { - top: parent.top - right: parent.right - rightMargin: (-0.5 * width) | 0 - topMargin: (-0.5 * height) | 0 + horizontalCenter: parent.right + verticalCenter: parent.top } labelText: activeScriptsList.count diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index a47f8e963c..cc400252e1 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -174,10 +174,8 @@ Item { anchors { - top: parent.top - right: parent.right - rightMargin: (-0.5 * width) | 0 - topMargin: (-0.5 * height) | 0 + horizontalCenter: parent.right + verticalCenter: parent.top } visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 -- cgit v1.2.3 From 408b649db7a36fd37f1bed9cbdf20bc8bb9a2c7c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 10:22:36 +0100 Subject: Add typing to tabmanager CURA-8588 --- plugins/Marketplace/Marketplace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index ea1f94be1d..dee2e0f4ac 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -24,12 +24,12 @@ class Marketplace(Extension): class TabManager(QObject): def __init__(self) -> None: super().__init__(parent=CuraApplication.getInstance()) - self._tab_shown = 0 + self._tab_shown: int = 0 - def getTabShown(self): + def getTabShown(self) -> int: return self._tab_shown - def setTabShown(self, tab_shown): + def setTabShown(self, tab_shown: int) -> None: if tab_shown != self._tab_shown: self._tab_shown = tab_shown self.tabShownChanged.emit() -- cgit v1.2.3 From 0615369cba94ad4671b7e8817cd168f464517f89 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 10:29:15 +0100 Subject: Clean up import order CURA-8588 --- plugins/Marketplace/LocalPackageList.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index ab28e634c2..7846236bf9 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,17 +1,11 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import Any, Dict, List, Optional, TYPE_CHECKING -from operator import attrgetter from PyQt5.QtCore import pyqtSlot, QObject from UM.Version import Version - -if TYPE_CHECKING: - from PyQt5.QtCore import QObject - from PyQt5.QtNetwork import QNetworkReply - from UM.i18n import i18nCatalog from UM.TaskManagement.HttpRequestManager import HttpRequestManager from UM.Logger import Logger @@ -20,6 +14,10 @@ from .PackageList import PackageList from .PackageModel import PackageModel from .Constants import PACKAGE_UPDATES_URL +if TYPE_CHECKING: + from PyQt5.QtCore import QObject + from PyQt5.QtNetwork import QNetworkReply + catalog = i18nCatalog("cura") -- cgit v1.2.3 From 3b2be48390abfb465cd85446662cfe253e723507 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:01:17 +0100 Subject: Ensure that check for updates is called on startup of Cura CURA-8588 --- plugins/Marketplace/Marketplace.py | 40 ++++++++++++++++++---- plugins/Marketplace/PackageList.py | 7 ++-- .../Marketplace/resources/qml/ManagedPackages.qml | 4 +-- plugins/Marketplace/resources/qml/Materials.qml | 5 +-- plugins/Marketplace/resources/qml/Plugins.qml | 5 +-- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index dee2e0f4ac..9bc1a2713c 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -16,7 +16,7 @@ from .LocalPackageList import LocalPackageList # To register this type with QML from .RestartManager import RestartManager # To register this type with QML. -class Marketplace(Extension): +class Marketplace(Extension, QObject): """ The main managing object for the Marketplace plug-in. """ @@ -37,16 +37,44 @@ class Marketplace(Extension): tabShownChanged = pyqtSignal() tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged) - def __init__(self) -> None: - super().__init__() + def __init__(self, parent: Optional[QObject] = None) -> None: + QObject.__init__(self, parent) + Extension.__init__(self) self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. self._plugin_registry: Optional[PluginRegistry] = None self._tab_manager = Marketplace.TabManager() + self._package_manager = CuraApplication.getInstance().getPackageManager() + + self._material_package_list: Optional[RemotePackageList] = None + self._plugin_package_list: Optional[RemotePackageList] = None + + # Not entirely the cleanest code, since the localPackage list also checks the server if there are updates + # Since that in turn will trigger notifications to be shown, we do need to construct it here and make sure + # that it checks for updates... + self._local_package_list = LocalPackageList(self) + self._local_package_list.checkForUpdates(self._package_manager.local_packages) - qmlRegisterType(RemotePackageList, "Marketplace", 1, 0, "RemotePackageList") - qmlRegisterType(LocalPackageList, "Marketplace", 1, 0, "LocalPackageList") qmlRegisterType(RestartManager, "Marketplace", 1, 0, "RestartManager") + @pyqtProperty(QObject, constant=True) + def MaterialPackageList(self): + if self._material_package_list is None: + self._material_package_list = RemotePackageList() + self._material_package_list.packageTypeFilter = "material" + + return self._material_package_list + + @pyqtProperty(QObject, constant=True) + def PluginPackageList(self): + if self._plugin_package_list is None: + self._plugin_package_list = RemotePackageList() + self._plugin_package_list.packageTypeFilter = "plugin" + return self._plugin_package_list + + @pyqtProperty(QObject, constant=True) + def LocalPackageList(self): + return self._local_package_list + @pyqtSlot() def show(self) -> None: """ @@ -60,7 +88,7 @@ class Marketplace(Extension): if plugin_path is None: plugin_path = os.path.dirname(__file__) path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {"tabManager": self._tab_manager}) + self._window = CuraApplication.getInstance().createQmlComponent(path, {"tabManager": self._tab_manager, "manager": self}) if self._window is None: # Still None? Failed to load the QML then. return self._tab_manager.setTabShown(0) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index ddc39e0c94..6d8ab3e4c6 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -132,9 +132,12 @@ class PackageList(ListModel): :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise""" return self._has_footer - def getPackageModel(self, package_id: str) -> PackageModel: + def getPackageModel(self, package_id: str) -> Optional[PackageModel]: index = self.find("package", package_id) - return self.getItem(index)["package"] + data = self.getItem(index) + if data: + return data.get("package") + return None def _openLicenseDialog(self, package_id: str, license_content: str) -> None: plugin_path = self._plugin_registry.getPluginPath("Marketplace") diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index dbdc04bf52..7c3d3ecfa2 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -22,7 +22,5 @@ Packages searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" packagesManageableInListView: true - model: Marketplace.LocalPackageList - { - } + model: manager.LocalPackageList } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index d19f3a4b04..03abd94077 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -19,8 +19,5 @@ Packages searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-materials-browser" packagesManageableInListView: false - model: Marketplace.RemotePackageList - { - packageTypeFilter: "material" - } + model: manager.MaterialPackageList } diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 3cfa92d134..5e8f459e77 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -19,8 +19,5 @@ Packages searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser" packagesManageableInListView: false - model: Marketplace.RemotePackageList - { - packageTypeFilter: "plugin" - } + model: manager.PluginPackageList } -- cgit v1.2.3 From 6af2677c5223e0f68cc1221605e0e76ae0e38d63 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:06:19 +0100 Subject: Remove TabManager Since marketplace itself already needed to be a qObject, there wasn't really a need to have a seperate object for it --- plugins/Marketplace/Marketplace.py | 37 ++++++++++------------- plugins/Marketplace/resources/qml/Marketplace.qml | 4 +-- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 9bc1a2713c..515d9959ed 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -20,29 +20,11 @@ class Marketplace(Extension, QObject): """ The main managing object for the Marketplace plug-in. """ - - class TabManager(QObject): - def __init__(self) -> None: - super().__init__(parent=CuraApplication.getInstance()) - self._tab_shown: int = 0 - - def getTabShown(self) -> int: - return self._tab_shown - - def setTabShown(self, tab_shown: int) -> None: - if tab_shown != self._tab_shown: - self._tab_shown = tab_shown - self.tabShownChanged.emit() - - tabShownChanged = pyqtSignal() - tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged) - def __init__(self, parent: Optional[QObject] = None) -> None: QObject.__init__(self, parent) Extension.__init__(self) self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. self._plugin_registry: Optional[PluginRegistry] = None - self._tab_manager = Marketplace.TabManager() self._package_manager = CuraApplication.getInstance().getPackageManager() self._material_package_list: Optional[RemotePackageList] = None @@ -54,8 +36,21 @@ class Marketplace(Extension, QObject): self._local_package_list = LocalPackageList(self) self._local_package_list.checkForUpdates(self._package_manager.local_packages) + self._tab_shown: int = 0 + qmlRegisterType(RestartManager, "Marketplace", 1, 0, "RestartManager") + def getTabShown(self) -> int: + return self._tab_shown + + def setTabShown(self, tab_shown: int) -> None: + if tab_shown != self._tab_shown: + self._tab_shown = tab_shown + self.tabShownChanged.emit() + + tabShownChanged = pyqtSignal() + tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged) + @pyqtProperty(QObject, constant=True) def MaterialPackageList(self): if self._material_package_list is None: @@ -88,10 +83,10 @@ class Marketplace(Extension, QObject): if plugin_path is None: plugin_path = os.path.dirname(__file__) path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {"tabManager": self._tab_manager, "manager": self}) + self._window = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if self._window is None: # Still None? Failed to load the QML then. return - self._tab_manager.setTabShown(0) + self.setTabShown(0) self._window.show() self._window.requestActivate() # Bring window into focus, if it was already open in the background. @@ -101,4 +96,4 @@ class Marketplace(Extension, QObject): Set the tab shown to the remote materials one. Not implemented in a more generic way because it needs the ability to be called with 'callExtensionMethod'. """ - self._tab_manager.setTabShown(1) + self.setTabShown(1) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 9027a02121..b909c88877 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -130,11 +130,11 @@ Window height: UM.Theme.getSize("button_icon").height spacing: 0 background: Rectangle { color: "transparent" } - currentIndex: tabManager.tabShown + currentIndex: manager.tabShown onCurrentIndexChanged: { - tabManager.tabShown = currentIndex + manager.tabShown = currentIndex searchBar.text = ""; searchBar.visible = currentItem.hasSearch; content.source = currentItem.sourcePage; -- cgit v1.2.3 From 6a398623491bc12509e561987af13da2d8f94788 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:12:52 +0100 Subject: Merge RestartManager into marketplace CURA-8588 --- plugins/Marketplace/Marketplace.py | 21 +++++++++++-- plugins/Marketplace/RestartManager.py | 36 ---------------------- .../Marketplace/resources/qml/ManagedPackages.qml | 1 - plugins/Marketplace/resources/qml/Marketplace.qml | 4 +-- plugins/Marketplace/resources/qml/Materials.qml | 1 - plugins/Marketplace/resources/qml/Plugins.qml | 1 - 6 files changed, 19 insertions(+), 45 deletions(-) delete mode 100644 plugins/Marketplace/RestartManager.py diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 515d9959ed..0ba17578d3 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -13,7 +13,6 @@ from UM.PluginRegistry import PluginRegistry # To find out where we are stored from .RemotePackageList import RemotePackageList # To register this type with QML. from .LocalPackageList import LocalPackageList # To register this type with QML. -from .RestartManager import RestartManager # To register this type with QML. class Marketplace(Extension, QObject): @@ -36,9 +35,10 @@ class Marketplace(Extension, QObject): self._local_package_list = LocalPackageList(self) self._local_package_list.checkForUpdates(self._package_manager.local_packages) - self._tab_shown: int = 0 + self._package_manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) - qmlRegisterType(RestartManager, "Marketplace", 1, 0, "RestartManager") + self._tab_shown: int = 0 + self._restart_needed = False def getTabShown(self) -> int: return self._tab_shown @@ -79,6 +79,7 @@ class Marketplace(Extension, QObject): """ if self._window is None: self._plugin_registry = PluginRegistry.getInstance() + self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) if plugin_path is None: plugin_path = os.path.dirname(__file__) @@ -97,3 +98,17 @@ class Marketplace(Extension, QObject): Not implemented in a more generic way because it needs the ability to be called with 'callExtensionMethod'. """ self.setTabShown(1) + + def checkIfRestartNeeded(self) -> None: + if self._package_manager.hasPackagesToRemoveOrInstall or len( + self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: + self._restart_needed = True + else: + self._restart_needed = False + self.showRestartNotificationChanged.emit() + + showRestartNotificationChanged = pyqtSignal() + + @pyqtProperty(bool, notify=showRestartNotificationChanged) + def showRestartNotification(self) -> bool: + return self._restart_needed diff --git a/plugins/Marketplace/RestartManager.py b/plugins/Marketplace/RestartManager.py deleted file mode 100644 index 9fe52b4116..0000000000 --- a/plugins/Marketplace/RestartManager.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2021 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional, TYPE_CHECKING - -from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject - -from cura.CuraApplication import CuraApplication - -if TYPE_CHECKING: - from UM.PluginRegistry import PluginRegistry - from cura.CuraPackageManager import CuraPackageManager - - -class RestartManager(QObject): - def __init__(self, parent: Optional[QObject] = None) -> None: - super().__init__(parent = parent) - self._manager: "CuraPackageManager" = CuraApplication.getInstance().getPackageManager() - self._plugin_registry: "PluginRegistry" = CuraApplication.getInstance().getPluginRegistry() - - self._manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) - self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) - - self._restart_needed = False - - def checkIfRestartNeeded(self) -> None: - if self._manager.hasPackagesToRemoveOrInstall or len(self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: - self._restart_needed = True - else: - self._restart_needed = False - self.showRestartNotificationChanged.emit() - - showRestartNotificationChanged = pyqtSignal() - - @pyqtProperty(bool, notify = showRestartNotificationChanged) - def showRestartNotification(self) -> bool: - return self._restart_needed diff --git a/plugins/Marketplace/resources/qml/ManagedPackages.qml b/plugins/Marketplace/resources/qml/ManagedPackages.qml index 7c3d3ecfa2..8ccaacea46 100644 --- a/plugins/Marketplace/resources/qml/ManagedPackages.qml +++ b/plugins/Marketplace/resources/qml/ManagedPackages.qml @@ -4,7 +4,6 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import Marketplace 1.0 as Marketplace import UM 1.4 as UM Packages diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index b909c88877..55457b8589 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -8,13 +8,11 @@ import QtQuick.Window 2.2 import UM 1.2 as UM import Cura 1.6 as Cura -import Marketplace 1.0 as Marketplace Window { id: marketplaceDialog property variant catalog: UM.I18nCatalog { name: "cura" } - property variant restartManager: Marketplace.RestartManager { } signal searchStringChanged(string new_search) @@ -235,7 +233,7 @@ Window { height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width color: UM.Theme.getColor("primary") - visible: restartManager.showRestartNotification + visible: manager.showRestartNotification anchors { left: parent.left diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 03abd94077..39fae7042a 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -1,7 +1,6 @@ // Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import Marketplace 1.0 as Marketplace import UM 1.4 as UM Packages diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 5e8f459e77..9983a827d8 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -1,7 +1,6 @@ // Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import Marketplace 1.0 as Marketplace import UM 1.4 as UM Packages -- cgit v1.2.3 From 852076460f3630f78ff01bf11fd2c2458a344bf0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:13:28 +0100 Subject: Remove unused imports CURA-8588 --- plugins/Marketplace/Marketplace.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 0ba17578d3..28dcb9259c 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -1,10 +1,9 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os.path from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject -from PyQt5.QtQml import qmlRegisterType -from typing import Optional, TYPE_CHECKING +from typing import Optional from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. -- cgit v1.2.3 From 89c82964c348ae97f0bfa4226774a64260109977 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:14:28 +0100 Subject: Simplify restart check CURA-8588 --- plugins/Marketplace/Marketplace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 28dcb9259c..980044c7c9 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -99,8 +99,8 @@ class Marketplace(Extension, QObject): self.setTabShown(1) def checkIfRestartNeeded(self) -> None: - if self._package_manager.hasPackagesToRemoveOrInstall or len( - self._plugin_registry.getCurrentSessionActivationChangedPlugins()) > 0: + if self._package_manager.hasPackagesToRemoveOrInstall or \ + self._plugin_registry.getCurrentSessionActivationChangedPlugins(): self._restart_needed = True else: self._restart_needed = False -- cgit v1.2.3 From 60e6d7bcaedc45d2962954cfbd658f104ad56790 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:20:27 +0100 Subject: Fix too large clickable area for author info in package card CURA-8588 --- .../resources/qml/PackageCardHeader.qml | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 0bf93fc67c..3a76f7a959 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -135,21 +135,23 @@ Item } // clickable author name - Cura.TertiaryButton + Item { Layout.fillWidth: true - Layout.preferredHeight: authorBy.height - Layout.alignment: Qt.AlignCenter - - text: packageData.authorName - textFont: UM.Theme.getFont("default_bold") - textColor: UM.Theme.getColor("text") // override normal link color - leftPadding: 0 - rightPadding: 0 - iconSource: UM.Theme.getIcon("LinkExternal") - isIconOnRightSide: true - - onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + implicitHeight: authorBy.height + Layout.alignment: Qt.AlignTop + Cura.TertiaryButton + { + text: packageData.authorName + textFont: UM.Theme.getFont("default_bold") + textColor: UM.Theme.getColor("text") // override normal link color + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(packageData.authorInfoUrl) + } } ManageButton -- cgit v1.2.3 From 27e5905a32a477965d01c890952321d110e837ca Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 3 Jan 2022 11:44:05 +0100 Subject: Fix typing CURA-8588 --- plugins/Marketplace/LocalPackageList.py | 3 ++- plugins/Marketplace/Marketplace.py | 4 ++-- plugins/Marketplace/PackageList.py | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 7846236bf9..a609e72d33 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -52,7 +52,8 @@ class LocalPackageList(PackageList): be updated, it is in the to remove list and isn't in the to be installed list """ package = self.getPackageModel(package_id) - if not package.canUpdate and \ + + if package and not package.canUpdate and \ package_id in self._package_manager.getToRemovePackageIDs() and \ package_id not in self._package_manager.getPackagesToInstall(): index = self.find("package", package_id) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 980044c7c9..8ce062394e 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -3,7 +3,7 @@ import os.path from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject -from typing import Optional +from typing import Optional, cast from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. @@ -100,7 +100,7 @@ class Marketplace(Extension, QObject): def checkIfRestartNeeded(self) -> None: if self._package_manager.hasPackagesToRemoveOrInstall or \ - self._plugin_registry.getCurrentSessionActivationChangedPlugins(): + cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins(): self._restart_needed = True else: self._restart_needed = False diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 6d8ab3e4c6..04b602002c 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -53,7 +53,6 @@ class PackageList(ListModel): def __del__(self) -> None: """ When this object is deleted it will loop through all registered API requests and aborts them """ - try: self.isLoadingChanged.disconnect() self.hasMoreChanged.disconnect() @@ -192,7 +191,10 @@ class PackageList(ListModel): Logger.warning(f"Could not install {package_id}") return package = self.getPackageModel(package_id) - self.subscribeUserToPackage(package_id, str(package.sdk_version)) + if package: + self.subscribeUserToPackage(package_id, str(package.sdk_version)) + else: + Logger.log("w", f"Unable to get data on package {package_id}") def download(self, package_id: str, url: str, update: bool = False) -> None: """Initiate the download request @@ -283,7 +285,8 @@ class PackageList(ListModel): self.download(package_id, url, False) else: package = self.getPackageModel(package_id) - self.subscribeUserToPackage(package_id, str(package.sdk_version)) + if package: + self.subscribeUserToPackage(package_id, str(package.sdk_version)) def uninstallPackage(self, package_id: str) -> None: """Uninstall a package from the Marketplace -- cgit v1.2.3 From 61a7203726adadf519f74041f59082514ba79f22 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 4 Jan 2022 13:13:31 +0100 Subject: Only reset marketplace page if the window isn't visible CURA-8588 --- plugins/Marketplace/Marketplace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 8ce062394e..2d98947572 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -86,7 +86,8 @@ class Marketplace(Extension, QObject): self._window = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if self._window is None: # Still None? Failed to load the QML then. return - self.setTabShown(0) + if not self._window.isVisible(): + self.setTabShown(0) self._window.show() self._window.requestActivate() # Bring window into focus, if it was already open in the background. -- cgit v1.2.3