diff --git a/plugins/Toolbox/resources/qml/Toolbox.qml b/plugins/Toolbox/resources/qml/Toolbox.qml
index ca00a4a8c5..e965aca05a 100644
--- a/plugins/Toolbox/resources/qml/Toolbox.qml
+++ b/plugins/Toolbox/resources/qml/Toolbox.qml
@@ -44,7 +44,7 @@ Window
ToolboxLoadingPage
{
id: viewLoading
- visible: manager.viewCategory != "installed" && !dataReady
+ visible: manager.viewCategory != "installed" && manager.viewPage == "loading"
// TODO: Replace !dataReady with manager.viewPage == "loading"
}
ToolboxDownloadsPage
@@ -65,8 +65,7 @@ Window
ToolboxInstalledPage
{
id: installedPluginList
- visible: manager.viewCategory == "installed" && dataReady
- // TODO: Replace !dataReady with manager.viewPage == "loading"
+ visible: manager.viewCategory == "installed"
}
}
ToolboxShadow
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
index 621650a42a..3221602bc0 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
@@ -34,7 +34,7 @@ Column
Repeater
{
- model: manager.materialShowcaseModel
+ model: manager.pluginsShowcaseModel
delegate: ToolboxDownloadsShowcaseTile {}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
index b7ec3dad2c..cb17ccadd3 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
@@ -1,33 +1,102 @@
// Copyright (c) 2018 Ultimaker B.V.
// PluginBrowser is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.7
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
ScrollView
{
- anchors.fill: parent
- ListView
+ id: base
+ frameVisible: true
+ width: parent.width
+ height: parent.height
+ style: UM.Theme.styles.scrollview
+ Column
{
- id: pluginList
- property var activePlugin
- property var filter: "installed"
+ spacing: UM.Theme.getSize("default_margin").height
anchors
{
- fill: parent
- topMargin: UM.Theme.getSize("default_margin").height
- bottomMargin: UM.Theme.getSize("default_margin").height
- leftMargin: UM.Theme.getSize("default_margin").width
- rightMargin: UM.Theme.getSize("default_margin").width
+ right: parent.right
+ left: parent.left
+ leftMargin: UM.Theme.getSize("double_margin").width
+ topMargin: UM.Theme.getSize("double_margin").height
+ bottomMargin: UM.Theme.getSize("double_margin").height
+ top: parent.top
+ }
+ height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
+ Label
+ {
+ width: parent.width
+ text: "Plugins"
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("medium")
+ }
+ Rectangle
+ {
+ color: "transparent"
+ width: parent.width
+ height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("text_medium")
+ border.width: UM.Theme.getSize("default_lining").width
+ Column
+ {
+ height: childrenRect.height
+ anchors
+ {
+ top: parent.top
+ right: parent.right
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ rightMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_lining").width
+ bottomMargin: UM.Theme.getSize("default_lining").width
+ }
+ Repeater
+ {
+ id: materialList
+ model: manager.packagesModel
+ delegate: ToolboxInstalledTile {}
+ }
+ }
+ }
+ Label
+ {
+ width: base.width
+ text: "Materials"
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("medium")
+ }
+ Rectangle
+ {
+ color: "transparent"
+ width: parent.width
+ height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("text_medium")
+ border.width: UM.Theme.getSize("default_lining").width
+ Column
+ {
+ height: childrenRect.height
+ anchors
+ {
+ top: parent.top
+ right: parent.right
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ rightMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_lining").width
+ bottomMargin: UM.Theme.getSize("default_lining").width
+ }
+ Repeater
+ {
+ id: pluginList
+ model: manager.packagesModel
+ delegate: ToolboxInstalledTile {}
+ }
+ }
}
- model: manager.pluginsModel
- delegate: ToolboxInstalledTile {}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
index 5ab4d1b2bf..9081801bb3 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
@@ -19,7 +19,7 @@ Component {
// Don't show required plugins as they can't be managed anyway:
height: !model.required ? 84 : 0
visible: !model.required ? true : false
- color: Qt.rgba(1.0, 0.0, 0.0, 0.1)
+ // color: Qt.rgba(1.0, 0.0, 0.0, 0.1)
anchors {
left: parent.left
right: parent.right
diff --git a/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml b/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
index 91d4cec013..0ef8585679 100644
--- a/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
@@ -14,5 +14,13 @@ Rectangle
id: base
width: parent.width
height: parent.height
- color: "red"
+ color: "transparent"
+ Label
+ {
+ text: "Fetching packages..."
+ anchors
+ {
+ centerIn: parent
+ }
+ }
}
diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py
index fef45dec7c..de1a7f6156 100644
--- a/plugins/Toolbox/src/Toolbox.py
+++ b/plugins/Toolbox/src/Toolbox.py
@@ -22,6 +22,7 @@ import platform
import zipfile
from cura.CuraApplication import CuraApplication
+from cura.CuraPackageManager import CuraPackageManager
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
@@ -33,6 +34,7 @@ class Toolbox(QObject, Extension):
super().__init__(parent)
self._plugin_registry = Application.getInstance().getPluginRegistry()
+ self._package_manager = None
self._packages_version = self._plugin_registry.APIVersion
self._api_version = 1
self._api_url = "https://api-staging.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version)
@@ -48,11 +50,14 @@ class Toolbox(QObject, Extension):
self._network_manager = None
- self._packages_metadata = [] # Stores the remote information of the packages
- self._packages_model = None # Model that list the remote available packages
- self._showcase_model = None
+ self._packages_metadata = []
+ self._packages_model = None
+ self._plugins_showcase_model = None
+ self._plugins_installed_model = None
+ self._materials_showcase_model = None
+ self._materials_installed_model = None
self._authors_model = None
- self._installed_model = None
+
# These properties are for keeping track of the UI state:
# ----------------------------------------------------------------------
@@ -66,7 +71,7 @@ class Toolbox(QObject, Extension):
# View page defines which type of page layout to use. For example,
# possible values include "overview", "detail" or "author".
# Formerly self._detail_view.
- self._view_page = "overview"
+ self._view_page = "loading"
# View selection defines what is currently selected and should be
# used in filtering. This could be an author name (if _view_page is set
@@ -155,9 +160,19 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def browsePackages(self):
- self._createNetworkManager()
- self.requestShowcase()
- self.requestPackages()
+ self._package_manager = Application.getInstance().getCuraPackageManager()
+ # Create the network manager:
+ # This was formerly its own function but really had no reason to be as
+ # it was never called more than once ever.
+ if self._network_manager:
+ self._network_manager.finished.disconnect(self._onRequestFinished)
+ self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
+ self._network_manager = QNetworkAccessManager()
+ self._network_manager.finished.connect(self._onRequestFinished)
+ self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
+
+ self._requestShowcase()
+ self._requestPackages()
if not self._dialog:
self._dialog = self._createDialog("Toolbox.qml")
self._dialog.show()
@@ -224,8 +239,8 @@ class Toolbox(QObject, Extension):
return self._plugins_model
@pyqtProperty(QObject, notify = showcaseMetadataChanged)
- def materialShowcaseModel(self):
- return self._showcase_model
+ def pluginsShowcaseModel(self):
+ return self._plugins_showcase_model
@pyqtProperty(QObject, notify = packagesMetadataChanged)
def packagesModel(self):
@@ -239,6 +254,18 @@ class Toolbox(QObject, Extension):
def dataReady(self):
return self._packages_model is not None
+ @pyqtProperty(bool, notify = restartRequiredChanged)
+ def restartRequired(self):
+ return self._restart_required
+
+ @pyqtSlot()
+ def restart(self):
+ CuraApplication.getInstance().windowClosed()
+
+
+
+ # Checks
+ # --------------------------------------------------------------------------
def _checkCanUpgrade(self, id, version):
# Scan plugin server data for plugin with the given id:
for plugin in self._packages_metadata:
@@ -250,57 +277,32 @@ class Toolbox(QObject, Extension):
return True
return False
- def _checkAlreadyInstalled(self, id):
- metadata = self._plugin_registry.getMetaData(id)
- # We already installed this plugin, but the registry just doesn't know it yet.
- if id in self._newly_installed_plugin_ids:
- return True
- # We already uninstalled this plugin, but the registry just doesn't know it yet:
- elif id in self._newly_uninstalled_plugin_ids:
+ def _checkInstalled(self, id):
+ if id in self._will_uninstall:
return False
- elif metadata != {}:
+ if id in self._package_manager.getInstalledPackages():
return True
- else:
- return False
-
- def _checkInstallStatus(self, plugin_id):
- if plugin_id in self._plugin_registry.getInstalledPlugins():
- return "installed"
- else:
- return "uninstalled"
+ if id in self._will_install:
+ return True
+ return False
def _checkEnabled(self, id):
if id in self._plugin_registry.getActivePlugins():
return True
return False
- def _createNetworkManager(self):
- if self._network_manager:
- self._network_manager.finished.disconnect(self._onRequestFinished)
- self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
- self._network_manager = QNetworkAccessManager()
- self._network_manager.finished.connect(self._onRequestFinished)
- self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
-
- @pyqtProperty(bool, notify = restartRequiredChanged)
- def restartRequired(self):
- return self._restart_required
-
- @pyqtSlot()
- def restart(self):
- CuraApplication.getInstance().windowClosed()
# Make API Calls
# --------------------------------------------------------------------------
- def requestPackages(self):
+ def _requestPackages(self):
Logger.log("i", "Toolbox: Requesting package list from server.")
url = QUrl("{base_url}/packages".format(base_url = self._api_url))
self._get_packages_request = QNetworkRequest(url)
self._get_packages_request.setRawHeader(*self._request_header)
self._network_manager.get(self._get_packages_request)
- def requestShowcase(self):
+ def _requestShowcase(self):
Logger.log("i", "Toolbox: Requesting showcase list from server.")
url = QUrl("{base_url}/showcase".format(base_url = self._api_url))
self._get_showcase_request = QNetworkRequest(url)
@@ -381,6 +383,7 @@ class Toolbox(QObject, Extension):
self._authors_metadata.append(package["author"])
self._authors_model.setMetaData(self._authors_metadata)
self.authorsMetadataChanged.emit()
+ self.setViewPage("overview")
except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for package list.")
return
@@ -390,12 +393,12 @@ class Toolbox(QObject, Extension):
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Create packages model with all packages:
- if not self._showcase_model:
- self._showcase_model = PackagesModel()
+ if not self._plugins_showcase_model:
+ self._plugins_showcase_model = PackagesModel()
self._showcase_metadata = json_data["data"]
print(self._showcase_metadata)
- self._showcase_model.setPackagesMetaData(self._showcase_metadata)
- for package in self._showcase_model.items:
+ self._plugins_showcase_model.setPackagesMetaData(self._showcase_metadata)
+ for package in self._plugins_showcase_model.items:
print(package)
self.showcaseMetadataChanged.emit()
except json.decoder.JSONDecodeError:
@@ -406,63 +409,66 @@ class Toolbox(QObject, Extension):
pass
def _onDownloadProgress(self, bytes_sent, bytes_total):
- print("Downloading bytes:", bytes_total)
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
self.setDownloadProgress(new_progress)
if new_progress == 100.0:
- Logger.log("i", "Toolbox: Download complete.")
self.setIsDownloading(False)
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
-
# must not delete the temporary file on Windows
- self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curaplugin", delete = False)
+ 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(self._download_reply.readAll())
self._temp_plugin_file.close()
-
self._onDownloadComplete(file_path)
return
def _onDownloadComplete(self, file_path):
- with zipfile.ZipFile(file_path, "r") as zip_ref:
- plugin_id = None
- for file in zip_ref.infolist():
- if file.filename.endswith("/"):
- plugin_id = file.filename.strip("/")
- break
+ Logger.log("i", "Toolbox: Download complete.")
+ print(file_path)
+ if self._package_manager.isPackageFile(file_path):
+ self._package_manager.install(file_path)
+ return
+ else:
+ Logger.log("w", "Toolbox: Package was not a valid CuraPackage.")
- if plugin_id is None:
- msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from {0}", file_path)
- msg_title = i18n_catalog.i18nc("@info:tile", "Warning")
- self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title)
- return
-
- # find a potential license file
- plugin_root_dir = plugin_id + "/"
- license_file = None
- for f in zip_ref.infolist():
- # skip directories (with file_size = 0) and files not in the plugin directory
- if f.file_size == 0 or not f.filename.startswith(plugin_root_dir):
- continue
- file_name = os.path.basename(f.filename).lower()
- file_base_name, file_ext = os.path.splitext(file_name)
- if file_base_name in ["license", "licence"]:
- license_file = f.filename
- break
-
- # show a dialog for user to read and accept/decline the license
- if license_file is not None:
- Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id)
- license_content = zip_ref.read(license_file).decode('utf-8')
- self.openLicenseDialog(plugin_id, license_content, file_path)
- return
-
- # there is no license file, directly install the plugin
- self.installPlugin(file_path)
- return
+ # with zipfile.ZipFile(file_path, "r") as zip_ref:
+ # plugin_id = None
+ # for file in zip_ref.infolist():
+ # if file.filename.endswith("/"):
+ # plugin_id = file.filename.strip("/")
+ # break
+ #
+ # if plugin_id is None:
+ # msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from {0}", file_path)
+ # msg_title = i18n_catalog.i18nc("@info:tile", "Warning")
+ # self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title)
+ # return
+ #
+ # # find a potential license file
+ # plugin_root_dir = plugin_id + "/"
+ # license_file = None
+ # for f in zip_ref.infolist():
+ # # skip directories (with file_size = 0) and files not in the plugin directory
+ # if f.file_size == 0 or not f.filename.startswith(plugin_root_dir):
+ # continue
+ # file_name = os.path.basename(f.filename).lower()
+ # file_base_name, file_ext = os.path.splitext(file_name)
+ # if file_base_name in ["license", "licence"]:
+ # license_file = f.filename
+ # break
+ #
+ # # show a dialog for user to read and accept/decline the license
+ # if license_file is not None:
+ # Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id)
+ # license_content = zip_ref.read(license_file).decode('utf-8')
+ # self.openLicenseDialog(plugin_id, license_content, file_path)
+ # return
+ #
+ # # there is no license file, directly install the plugin
+ # self.installPlugin(file_path)
+ # return