diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 93f7fa97ff..d0f81920c7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1062,6 +1062,9 @@ class CuraApplication(QtApplication): qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel") + # from plugins.Toolbox.src.SubscribedPackagesModel import SubscribedPackagesModel + # qmlRegisterType(SubscribedPackagesModel, "Cura", 1, 6, "SubscribedPackagesModel") ### This might not be needed + qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml new file mode 100644 index 0000000000..ea39cb5e99 --- /dev/null +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -0,0 +1,39 @@ +// 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 1.4 +import QtQuick.Controls.Styles 1.4 + +// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles + +import UM 1.1 as UM +import Cura 1.6 as Cura + + +UM.Dialog +{ + visible: true + + title: "Some title" + minimumWidth: UM.Theme.getSize("license_window_minimum").width + minimumHeight: UM.Theme.getSize("license_window_minimum").height + width: minimumWidth + height: minimumHeight + + ListView + { + id: listView + anchors.fill: parent + + + model: toolbox.subscribedPackagesModel + + delegate: Label + { + text: "A :)" + } + } +} diff --git a/plugins/Toolbox/src/SubscribedPackagesModel.py b/plugins/Toolbox/src/SubscribedPackagesModel.py new file mode 100644 index 0000000000..1d2e98913b --- /dev/null +++ b/plugins/Toolbox/src/SubscribedPackagesModel.py @@ -0,0 +1,160 @@ +# Copyright (c) 2018 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 + + +from UM.PluginRegistry import PluginRegistry + +## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed. +class SubscribedPackagesModel(ListModel): + def __init__(self, parent = None): + super().__init__(parent) + + # self._metadata = None + + self.addRoleName(Qt.UserRole + 1, "name") + self.addRoleName(Qt.UserRole + 2, "icon_url") + self.addRoleName(Qt.UserRole + 3, "is_compatible") + + + # 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): + print("---- in update function") + items1 = [] + items2 = [] + + # if self._metadata is None: + # Logger.logException("w", "Failed to load packages for Marketplace") + # self.setItems(items) + # return + + toolbox = PluginRegistry.getInstance().getPluginObject("Toolbox") + print(toolbox.subscribed_compatible_packages) + print(toolbox.subscribed_incompatible_packages) + + for incompatible in toolbox.subscribed_incompatible_packages: + items1.append({ + "name": incompatible.package_id + }) + + for compatible in toolbox.subscribed_compatible_packages: + items2.append({ + "name": compatible.package_id + }) + print("======================0----------------------") + print(items1) + print(items2) + + # for package in self._metadata: + # has_configs = False + # configs_model = None + # + # links_dict = {} + # if "data" in package: + # 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"]) + # + # # 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} + # + # 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"], + # "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", []), + # "average_rating": float(package.get("rating", {}).get("average", 0)), + # "num_ratings": package.get("rating", {}).get("count", 0), + # "user_rating": package.get("rating", {}).get("user_rating", 0) + # }) + # + # # 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) + final_list = items1 + items2 + print(final_list) + self.setItems(final_list) + + ## Set the filter of this model based on a string. + # \param filter_dict \type{Dict} Dictionary to do the filtering by. + def setFilter(self, filter_dict: Dict[str, str]) -> None: + 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 index e3b41f9bbc..f5c15007e7 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -24,6 +24,9 @@ from cura.Machines.ContainerTree import ContainerTree from .AuthorsModel import AuthorsModel from .PackagesModel import PackagesModel +from .SubscribedPackagesModel import SubscribedPackagesModel + +from PyQt5.QtQml import qmlRegisterType if TYPE_CHECKING: from cura.Settings.GlobalStack import GlobalStack @@ -38,6 +41,9 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication + # self._application.qm + # qmlRegisterType(Toolbox, "Cura", 1, 6, "Toolbox") + self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: str self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str @@ -57,6 +63,9 @@ class Toolbox(QObject, Extension): self._old_plugin_ids = set() # type: Set[str] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] + self.subscribed_compatible_packages = [] # type: List[str] + self.subscribed_incompatible_packages = [] # type: List[str] + # The responses as given by the server parsed to a list. self._server_response_data = { "authors": [], @@ -70,8 +79,8 @@ class Toolbox(QObject, Extension): "authors": AuthorsModel(self), "packages": PackagesModel(self), "updates": PackagesModel(self), - "subscribed_packages": PackagesModel(self), - } # type: Dict[str, Union[AuthorsModel, PackagesModel]] + "subscribed_packages": SubscribedPackagesModel(self), + } # type: Dict[str, Union[AuthorsModel, PackagesModel, SubscribedPackagesModel]] self._plugins_showcase_model = PackagesModel(self) self._plugins_available_model = PackagesModel(self) @@ -679,14 +688,32 @@ class Toolbox(QObject, Extension): packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) elif response_type == "subscribed_packages": - user_subscribed = [(plugin["package_id"], plugin["package_version"]) for plugin in json_data["data"]] - Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) - user_installed = self._package_manager.getUserInstalledPackagesAndVersions() + + import collections + Package = collections.namedtuple("Package", ["package_id", "sdk_versions"]) + + user_subscribed = [Package(plugin['package_id'], plugin['sdk_versions']) for plugin in json_data["data"]] + user_subscribed_list = [plugin["package_id"] for plugin in json_data["data"]] + + self.subscribed_compatible_packages.clear() + self.subscribed_incompatible_packages.clear() + + for subscribed in user_subscribed: + if self._sdk_version not in subscribed.sdk_versions: + self.subscribed_incompatible_packages.append(subscribed) + else: + self.subscribed_compatible_packages.append(subscribed) + + + print("compatible packages: \n {}".format(self.subscribed_compatible_packages)) + print("incompatible packages: \n {}".format(self.subscribed_incompatible_packages)) + + self._models["subscribed_packages"].update() + + user_installed = self._package_manager.getUserInstalledPackages() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) - # Check for discrepancies between Cura installed and Cloud subscribed packages - # convert them to set() to check if they are equal - if set(user_installed) != set(user_subscribed): + if set(user_installed) != set(user_subscribed_list): Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", @@ -699,6 +726,7 @@ class Toolbox(QObject, Extension): description = "Sync your Cloud subscribed packages to your local environment.", button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) sync_message.show() + sync_message.actionTriggered.connect(self.some_function) self.metadataChanged.emit() @@ -716,6 +744,14 @@ class Toolbox(QObject, Extension): # Ignore any operation that is not a get operation pass + def some_function(self, messageId: str, actionId: str) -> None: + print("Clicked the BUTTON") + + compatibilityDialog = "resources/qml/dialogs/CompatibilityDialog.qml" + path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), compatibilityDialog) + self._view = self._application.getInstance().createQmlComponent(path, {"toolbox": self}) # what is toolbox: self + + # 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"]: @@ -814,6 +850,10 @@ class Toolbox(QObject, Extension): def authorsModel(self) -> AuthorsModel: return cast(AuthorsModel, self._models["authors"]) + @pyqtProperty(QObject, constant = True) + def subscribedPackagesModel(self) -> SubscribedPackagesModel: + return cast(SubscribedPackagesModel, self._models["subscribed_packages"]) + @pyqtProperty(QObject, constant = True) def packagesModel(self) -> PackagesModel: return cast(PackagesModel, self._models["packages"])