diff --git a/plugins/PluginBrowser/PluginBrowser.py b/plugins/PluginBrowser/PluginBrowser.py
index 13fa553779..034fb57398 100644
--- a/plugins/PluginBrowser/PluginBrowser.py
+++ b/plugins/PluginBrowser/PluginBrowser.py
@@ -1,26 +1,31 @@
# Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
+from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
+
+from UM.Application import Application
+from UM.Qt.ListModel import ListModel
+from UM.Logger import Logger
+from UM.PluginRegistry import PluginRegistry
+from UM.Qt.Bindings.PluginsModel import PluginsModel
from UM.Extension import Extension
from UM.i18n import i18nCatalog
-from UM.Logger import Logger
-from UM.Qt.ListModel import ListModel
-from UM.PluginRegistry import PluginRegistry
-from UM.Application import Application
+
from UM.Version import Version
from UM.Message import Message
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
-from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
-
import json
import os
import tempfile
import platform
import zipfile
+import shutil
+
+from cura.CuraApplication import CuraApplication
i18n_catalog = i18nCatalog("cura")
-
class PluginBrowser(QObject, Extension):
def __init__(self, parent=None):
super().__init__(parent)
@@ -34,11 +39,18 @@ class PluginBrowser(QObject, Extension):
self._download_plugin_reply = None
self._network_manager = None
+ self._plugin_registry = Application.getInstance().getPluginRegistry()
self._plugins_metadata = []
self._plugins_model = None
+ # Can be 'installed' or 'availble'
+ self._view = "available"
+
+ self._restart_required = False
+
self._dialog = None
+ self._restartDialog = None
self._download_progress = 0
self._is_downloading = False
@@ -52,16 +64,29 @@ class PluginBrowser(QObject, Extension):
)
]
- # Installed plugins are really installed after reboot. In order to prevent the user from downloading the
- # same file over and over again, we keep track of the upgraded plugins.
+ # Installed plugins are really installed after reboot. In order to
+ # prevent the user from downloading the same file over and over again,
+ # we keep track of the upgraded plugins.
+
+ # NOTE: This will be depreciated in favor of the 'status' system.
self._newly_installed_plugin_ids = []
+ self._newly_uninstalled_plugin_ids = []
+
+ self._plugin_statuses = {} # type: Dict[str, str]
# variables for the license agreement dialog
self._license_dialog_plugin_name = ""
self._license_dialog_license_content = ""
self._license_dialog_plugin_file_location = ""
+ self._restart_dialog_message = ""
showLicenseDialog = pyqtSignal()
+ showRestartDialog = pyqtSignal()
+ pluginsMetadataChanged = pyqtSignal()
+ onDownloadProgressChanged = pyqtSignal()
+ onIsDownloadingChanged = pyqtSignal()
+ restartRequiredChanged = pyqtSignal()
+ viewChanged = pyqtSignal()
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self):
@@ -75,15 +100,19 @@ class PluginBrowser(QObject, Extension):
def getLicenseDialogLicenseContent(self):
return self._license_dialog_license_content
+ @pyqtSlot(result = str)
+ def getRestartDialogMessage(self):
+ return self._restart_dialog_message
+
def openLicenseDialog(self, plugin_name, license_content, plugin_file_location):
self._license_dialog_plugin_name = plugin_name
self._license_dialog_license_content = license_content
self._license_dialog_plugin_file_location = plugin_file_location
self.showLicenseDialog.emit()
- pluginsMetadataChanged = pyqtSignal()
- onDownloadProgressChanged = pyqtSignal()
- onIsDownloadingChanged = pyqtSignal()
+ def openRestartDialog(self, message):
+ self._restart_dialog_message = message
+ self.showRestartDialog.emit()
@pyqtProperty(bool, notify = onIsDownloadingChanged)
def isDownloading(self):
@@ -179,17 +208,46 @@ class PluginBrowser(QObject, Extension):
@pyqtSlot(str)
def installPlugin(self, file_path):
+ # Ensure that it starts with a /, as otherwise it doesn't work on windows.
if not file_path.startswith("/"):
- location = "/" + file_path # Ensure that it starts with a /, as otherwise it doesn't work on windows.
+ location = "/" + file_path
else:
location = file_path
+
result = PluginRegistry.getInstance().installPlugin("file://" + location)
self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit()
+ self.openRestartDialog(result["message"])
+ self._restart_required = True
+ self.restartRequiredChanged.emit()
+ # Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
+
+ @pyqtSlot(str)
+ def removePlugin(self, plugin_id):
+ result = PluginRegistry.getInstance().uninstallPlugin(plugin_id)
+
+ self._newly_uninstalled_plugin_ids.append(result["id"])
+ self.pluginsMetadataChanged.emit()
+
+ self._restart_required = True
+ self.restartRequiredChanged.emit()
+
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
+ @pyqtSlot(str)
+ def enablePlugin(self, plugin_id):
+ self._plugin_registry.enablePlugin(plugin_id)
+ self.pluginsMetadataChanged.emit()
+ Logger.log("i", "%s was set as 'active'", id)
+
+ @pyqtSlot(str)
+ def disablePlugin(self, plugin_id):
+ self._plugin_registry.disablePlugin(plugin_id)
+ self.pluginsMetadataChanged.emit()
+ Logger.log("i", "%s was set as 'deactive'", id)
+
@pyqtProperty(int, notify = onDownloadProgressChanged)
def downloadProgress(self):
return self._download_progress
@@ -221,55 +279,72 @@ class PluginBrowser(QObject, Extension):
self.setDownloadProgress(0)
self.setIsDownloading(False)
+ @pyqtSlot(str)
+ def setView(self, view):
+ self._view = view
+ self.viewChanged.emit()
+ self.pluginsMetadataChanged.emit()
+
@pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self):
- if self._plugins_model is None:
- self._plugins_model = ListModel()
- self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
- self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
- self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description")
- self._plugins_model.addRoleName(Qt.UserRole + 4, "author")
- self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed")
- self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location")
- self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade")
- else:
- self._plugins_model.clear()
- items = []
- for metadata in self._plugins_metadata:
- items.append({
- "name": metadata["label"],
- "version": metadata["version"],
- "short_description": metadata["short_description"],
- "author": metadata["author"],
- "already_installed": self._checkAlreadyInstalled(metadata["id"]),
- "file_location": metadata["file_location"],
- "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"])
- })
- self._plugins_model.setItems(items)
+ print("Updating plugins model...", self._view)
+ self._plugins_model = PluginsModel(self._view)
+ # self._plugins_model.update()
+
+ # Check each plugin the registry for matching plugin from server
+ # metadata, and if found, compare the versions. Higher version sets
+ # 'can_upgrade' to 'True':
+ for plugin in self._plugins_model.items:
+ if self._checkCanUpgrade(plugin["id"], plugin["version"]):
+ plugin["can_upgrade"] = True
+ print(self._plugins_metadata)
+
+ for item in self._plugins_metadata:
+ if item["id"] == plugin["id"]:
+ plugin["update_url"] = item["file_location"]
+
return self._plugins_model
+
+
def _checkCanUpgrade(self, id, version):
- plugin_registry = PluginRegistry.getInstance()
- metadata = plugin_registry.getMetaData(id)
- if metadata != {}:
- if id in self._newly_installed_plugin_ids:
- return False # We already updated this plugin.
- current_version = Version(metadata["plugin"]["version"])
- new_version = Version(version)
- if new_version > current_version:
- return True
+
+ # TODO: This could maybe be done more efficiently using a dictionary...
+
+ # Scan plugin server data for plugin with the given id:
+ for plugin in self._plugins_metadata:
+ if id == plugin["id"]:
+ reg_version = Version(version)
+ new_version = Version(plugin["version"])
+ if new_version > reg_version:
+ Logger.log("i", "%s has an update availible: %s", plugin["id"], plugin["version"])
+ return True
return False
def _checkAlreadyInstalled(self, id):
- plugin_registry = PluginRegistry.getInstance()
- metadata = plugin_registry.getMetaData(id)
- if metadata != {}:
+ 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:
+ return False
+ elif metadata != {}:
return True
else:
- if id in self._newly_installed_plugin_ids:
- return True # We already installed this plugin, but the registry just doesn't know it yet.
return False
+ def _checkInstallStatus(self, plugin_id):
+ if plugin_id in self._plugin_registry.getInstalledPlugins():
+ return "installed"
+ else:
+ return "uninstalled"
+
+ def _checkEnabled(self, id):
+ if id in self._plugin_registry.getActivePlugins():
+ return True
+ return False
+
def _onRequestFinished(self, reply):
reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError:
@@ -290,7 +365,11 @@ class PluginBrowser(QObject, Extension):
if reply_url == self._api_url + "plugins":
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
+
+ # Add metadata to the manager:
self._plugins_metadata = json_data
+ print(self._plugins_metadata)
+ self._plugin_registry.addExternalPlugins(self._plugins_metadata)
self.pluginsMetadataChanged.emit()
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
@@ -316,3 +395,15 @@ class PluginBrowser(QObject, Extension):
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
+
+ @pyqtProperty(str, notify=viewChanged)
+ def viewing(self):
+ return self._view
+
+ @pyqtSlot()
+ def restart(self):
+ CuraApplication.getInstance().quit()
diff --git a/plugins/PluginBrowser/PluginBrowser.qml b/plugins/PluginBrowser/PluginBrowser.qml
index 13000d23ad..ec4c2a9135 100644
--- a/plugins/PluginBrowser/PluginBrowser.qml
+++ b/plugins/PluginBrowser/PluginBrowser.qml
@@ -1,191 +1,209 @@
-import UM 1.1 as UM
+// Copyright (c) 2017 Ultimaker B.V.
+// PluginBrowser is released under the terms of the LGPLv3 or higher.
+
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
-import QtQuick.Controls 1.1
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
-UM.Dialog
-{
+// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
+
+import UM 1.1 as UM
+
+Window {
id: base
- title: catalog.i18nc("@title:window", "Find & Update plugins")
- width: 600 * screenScaleFactor
- height: 450 * screenScaleFactor
+ title: catalog.i18nc("@title:tab", "Plugins");
+ width: 800 * screenScaleFactor
+ height: 640 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
- Item
- {
- anchors.fill: parent
- Item
- {
- id: topBar
- height: childrenRect.height;
- width: parent.width
- Label
- {
- id: introText
- text: catalog.i18nc("@label", "Here you can find a list of Third Party plugins.")
- width: parent.width
- height: 30
- }
+ color: UM.Theme.getColor("sidebar")
- Button
- {
- id: refresh
- text: catalog.i18nc("@action:button", "Refresh")
- onClicked: manager.requestPluginList()
- anchors.right: parent.right
- enabled: !manager.isDownloading
+ Item {
+ id: view
+ anchors {
+ fill: parent
+ leftMargin: UM.Theme.getSize("default_margin").width
+ rightMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+
+ Rectangle {
+ id: topBar
+ width: parent.width
+ color: "transparent"
+ height: childrenRect.height
+
+ Row {
+ spacing: 12
+ height: childrenRect.height
+ width: childrenRect.width
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Button {
+ text: "Install"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 48
+ Rectangle {
+ visible: manager.viewing == "available" ? true : false
+ color: UM.Theme.getColor("primary")
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 3
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ font {
+ pixelSize: 15
+ }
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.setView("available")
+ }
+
+ Button {
+ text: "Manage"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 48
+ Rectangle {
+ visible: manager.viewing == "installed" ? true : false
+ color: UM.Theme.getColor("primary")
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 3
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ font {
+ pixelSize: 15
+ }
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.setView("installed")
+ }
}
}
- ScrollView
- {
+
+ // Scroll view breaks in QtQuick.Controls 2.x
+ ScrollView {
+ id: installedPluginList
width: parent.width
- anchors.top: topBar.bottom
- anchors.bottom: bottomBar.top
- anchors.bottomMargin: UM.Theme.getSize("default_margin").height
+ height: 400
+
+ anchors {
+ top: topBar.bottom
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottom: bottomBar.top
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+
frameVisible: true
- ListView
- {
+
+ ListView {
id: pluginList
- model: manager.pluginsModel
+ property var activePlugin
+ property var filter: "installed"
+
anchors.fill: parent
- property var activePlugin
- delegate: pluginDelegate
+ model: manager.pluginsModel
+ delegate: PluginEntry {}
}
}
- Item
- {
+
+ Rectangle {
id: bottomBar
width: parent.width
- height: closeButton.height
+ height: childrenRect.height
+ color: "transparent"
anchors.bottom: parent.bottom
- anchors.left: parent.left
- ProgressBar
- {
- id: progressbar
- anchors.bottom: parent.bottom
- minimumValue: 0;
- maximumValue: 100
- anchors.left:parent.left
+
+ Label {
+ visible: manager.restartRequired
+ text: "You will need to restart Cura before changes in plugins have effect."
+ height: 30
+ verticalAlignment: Text.AlignVCenter
+ }
+ Button {
+ id: restartChangedButton
+ text: "Quit Cura"
anchors.right: closeButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
- value: manager.isDownloading ? manager.downloadProgress : 0
+ visible: manager.restartRequired
+ iconName: "dialog-restart"
+ onClicked: manager.restart()
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: UM.Theme.getColor("primary")
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("button_text")
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
}
- Button
- {
+ Button {
id: closeButton
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
- onClicked:
- {
- if (manager.isDownloading)
- {
+ onClicked: {
+ if ( manager.isDownloading ) {
manager.cancelDownload()
}
base.close();
}
- anchors.bottom: parent.bottom
anchors.right: parent.right
- }
- }
-
- Item
- {
- SystemPalette { id: palette }
- Component
- {
- id: pluginDelegate
- Rectangle
- {
- width: pluginList.width;
- height: texts.height;
- color: index % 2 ? palette.base : palette.alternateBase
- Column
- {
- id: texts
- width: parent.width
- height: childrenRect.height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: downloadButton.left
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- Label
- {
- text: "" + model.name + "" + ((model.author !== "") ? (" - " + model.author) : "")
- width: contentWidth
- height: contentHeight + UM.Theme.getSize("default_margin").height
- verticalAlignment: Text.AlignVCenter
- }
-
- Label
- {
- text: model.short_description
- width: parent.width
- height: contentHeight + UM.Theme.getSize("default_margin").height
- wrapMode: Text.WordWrap
- verticalAlignment: Text.AlignVCenter
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
}
}
- Button
- {
- id: downloadButton
- text:
- {
- if (manager.isDownloading && pluginList.activePlugin == model)
- {
- return catalog.i18nc("@action:button", "Cancel");
- }
- else if (model.already_installed)
- {
- if (model.can_upgrade)
- {
- return catalog.i18nc("@action:button", "Upgrade");
- }
- return catalog.i18nc("@action:button", "Installed");
- }
- return catalog.i18nc("@action:button", "Download");
- }
- onClicked:
- {
- if(!manager.isDownloading)
- {
- pluginList.activePlugin = model;
- manager.downloadAndInstallPlugin(model.file_location);
- }
- else
- {
- manager.cancelDownload();
- }
- }
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- enabled:
- {
- if (manager.isDownloading)
- {
- return (pluginList.activePlugin == model);
- }
- else
- {
- return (!model.already_installed || model.can_upgrade);
- }
- }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
}
}
-
}
}
+
UM.I18nCatalog { id: catalog; name: "cura" }
- Connections
- {
+ Connections {
target: manager
- onShowLicenseDialog:
- {
+ onShowLicenseDialog: {
licenseDialog.pluginName = manager.getLicenseDialogPluginName();
licenseDialog.licenseContent = manager.getLicenseDialogLicenseContent();
licenseDialog.pluginFileLocation = manager.getLicenseDialogPluginFileLocation();
@@ -193,8 +211,7 @@ UM.Dialog
}
}
- UM.Dialog
- {
+ UM.Dialog {
id: licenseDialog
title: catalog.i18nc("@title:window", "Plugin License Agreement")
@@ -258,5 +275,94 @@ UM.Dialog
}
]
}
+
+ Connections {
+ target: manager
+ onShowRestartDialog: {
+ restartDialog.message = manager.getRestartDialogMessage();
+ restartDialog.show();
+ }
+ }
+
+ Window {
+ id: restartDialog
+ // title: catalog.i18nc("@title:tab", "Plugins");
+ width: 360 * screenScaleFactor
+ height: 120 * screenScaleFactor
+ minimumWidth: 360 * screenScaleFactor
+ minimumHeight: 120 * screenScaleFactor
+ color: UM.Theme.getColor("sidebar")
+ property var message;
+
+ Text {
+ id: message
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ }
+ text: restartDialog.message != null ? restartDialog.message : ""
+ }
+ Button {
+ id: laterButton
+ text: "Later"
+ onClicked: restartDialog.close();
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+
+
+ Button {
+ id: restartButton
+ text: "Quit Cura"
+ anchors {
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+ onClicked: manager.restart()
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: UM.Theme.getColor("primary")
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("button_text")
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ }
+
}
}
diff --git a/plugins/PluginBrowser/PluginEntry.qml b/plugins/PluginBrowser/PluginEntry.qml
new file mode 100644
index 0000000000..85fc0b91b8
--- /dev/null
+++ b/plugins/PluginBrowser/PluginEntry.qml
@@ -0,0 +1,474 @@
+// Copyright (c) 2017 Ultimaker B.V.
+// PluginBrowser is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+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
+
+Component {
+ id: pluginDelegate
+
+ Rectangle {
+
+ // Don't show required plugins as they can't be managed anyway:
+ height: !model.required ? 84 : 0
+ visible: !model.required ? true : false
+ color: "transparent"
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+
+ // Bottom border:
+ Rectangle {
+ color: UM.Theme.getColor("lining")
+ width: parent.width
+ height: 1
+ anchors.bottom: parent.bottom
+ }
+
+ // Plugin info
+ Column {
+ id: pluginInfo
+
+ property var color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
+
+ // Styling:
+ height: parent.height
+ anchors {
+ left: parent.left
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ right: authorInfo.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+
+ Label {
+ text: model.name
+ width: parent.width
+ height: 24
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ color: pluginInfo.color
+
+ }
+
+ Text {
+ text: model.description
+ width: parent.width
+ height: 36
+ clip: true
+ wrapMode: Text.WordWrap
+ color: pluginInfo.color
+ elide: Text.ElideRight
+ }
+ }
+
+ // Author info
+ Column {
+ id: authorInfo
+ width: 192
+ height: parent.height
+ anchors {
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ right: pluginActions.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+ Label {
+ text: ""+model.author+""
+ width: parent.width
+ height: 24
+ 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")
+ }
+ }
+
+ // Plugin actions
+ Row {
+ id: pluginActions
+
+ width: 96
+ height: parent.height
+ anchors {
+ top: parent.top
+ right: parent.right
+ topMargin: UM.Theme.getSize("default_margin").height
+ }
+ layoutDirection: Qt.RightToLeft
+ spacing: UM.Theme.getSize("default_margin").width
+
+ // For 3rd-Party Plugins:
+ Button {
+ id: installButton
+ text: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ if (model.can_upgrade) {
+ return catalog.i18nc("@action:button", "Update");
+ }
+ return catalog.i18nc("@action:button", "Install");
+ }
+ }
+ visible: model.external && ((model.status !== "installed") || model.can_upgrade)
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Label {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ manager.cancelDownload();
+ } else {
+ pluginList.activePlugin = model;
+ if ( model.can_upgrade ) {
+ manager.downloadAndInstallPlugin( model.update_url );
+ } else {
+ manager.downloadAndInstallPlugin( model.file_location );
+ }
+
+ }
+ }
+ }
+ Button {
+ id: removeButton
+ text: "Uninstall"
+ visible: model.external && model.status == "installed"
+ enabled: !manager.isDownloading
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.removePlugin( model.id )
+ }
+
+ // For Ultimaker Plugins:
+ Button {
+ id: enableButton
+ text: "Enable"
+ visible: !model.external && model.enabled == false
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ manager.enablePlugin(model.id);
+ }
+ }
+ Button {
+ id: disableButton
+ text: "Disable"
+ visible: !model.external && model.enabled == true
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ manager.disablePlugin(model.id);
+ }
+ }
+ /*
+ Rectangle {
+ id: removeControls
+ visible: model.status == "installed" && model.enabled
+ width: 96
+ height: 30
+ color: "transparent"
+ Button {
+ id: removeButton
+ text: "Disable"
+ enabled: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return false;
+ } else if ( model.required ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ onClicked: {
+ manager.disablePlugin(model.id);
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "white"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+ }
+ Button {
+ id: removeDropDown
+ property bool open: false
+ UM.RecolorImage {
+ anchors.centerIn: parent
+ height: 10
+ width: 10
+ source: UM.Theme.getIcon("arrow_bottom")
+ color: "grey"
+ }
+ enabled: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return false;
+ } else if ( model.required ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ anchors.right: parent.right
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 30
+ implicitHeight: 30
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+
+
+ // For the disable option:
+ // onClicked: pluginList.model.setEnabled(model.id, checked)
+
+ onClicked: {
+ if ( !removeDropDown.open ) {
+ removeDropDown.open = true
+ }
+ else {
+ removeDropDown.open = false
+ }
+ }
+ }
+
+ Rectangle {
+ id: divider
+ width: 1
+ height: parent.height
+ anchors.right: removeDropDown.left
+ color: UM.Theme.getColor("lining")
+ }
+
+ Column {
+ id: options
+ anchors {
+ top: removeButton.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: childrenRect.height
+ visible: removeDropDown.open
+
+ Button {
+ id: disableButton
+ text: "Remove"
+ height: 30
+ width: parent.width
+ onClicked: {
+ removeDropDown.open = false;
+ manager.removePlugin( model.id );
+ }
+ }
+ }
+ }
+ */
+ /*
+ Button {
+ id: enableButton
+ visible: !model.enabled && model.status == "installed"
+ onClicked: manager.enablePlugin( model.id );
+
+ text: "Enable"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+
+ Button {
+ id: updateButton
+ visible: model.status == "installed" && model.can_upgrade && model.enabled
+ // visible: model.already_installed
+ text: {
+ // If currently downloading:
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ return catalog.i18nc("@action:button", "Update");
+ }
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: UM.Theme.getColor("primary")
+ implicitWidth: 96
+ implicitHeight: 30
+ // radius: 4
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "white"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ Button {
+ id: externalControls
+ visible: model.status == "available" ? true : false
+ text: {
+ // If currently downloading:
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ return catalog.i18nc("@action:button", "Install");
+ }
+ }
+ onClicked: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ manager.cancelDownload();
+ } else {
+ pluginList.activePlugin = model;
+ manager.downloadAndInstallPlugin( model.file_location );
+ }
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ */
+ ProgressBar {
+ id: progressbar
+ minimumValue: 0;
+ maximumValue: 100
+ anchors.left: installButton.left
+ anchors.right: installButton.right
+ anchors.top: installButton.bottom
+ anchors.topMargin: 4
+ value: manager.isDownloading ? manager.downloadProgress : 0
+ visible: manager.isDownloading && pluginList.activePlugin == model
+ style: ProgressBarStyle {
+ background: Rectangle {
+ color: "lightgray"
+ implicitHeight: 6
+ }
+ progress: Rectangle {
+ color: UM.Theme.getColor("primary")
+ }
+ }
+ }
+
+ }
+ }
+}