Merge pull request #3203 from Ultimaker/plugin-browser

Plugin browser
This commit is contained in:
Lipu Fei 2018-02-01 11:51:50 +01:00 committed by GitHub
commit 91c9a82627
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 870 additions and 199 deletions

View file

@ -1,26 +1,31 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the LGPLv3 or higher. # 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.Extension import Extension
from UM.i18n import i18nCatalog 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.Version import Version
from UM.Message import Message 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 json
import os import os
import tempfile import tempfile
import platform import platform
import zipfile import zipfile
import shutil
from cura.CuraApplication import CuraApplication
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
class PluginBrowser(QObject, Extension): class PluginBrowser(QObject, Extension):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -34,11 +39,18 @@ class PluginBrowser(QObject, Extension):
self._download_plugin_reply = None self._download_plugin_reply = None
self._network_manager = None self._network_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._plugins_metadata = [] self._plugins_metadata = []
self._plugins_model = None self._plugins_model = None
# Can be 'installed' or 'availble'
self._view = "available"
self._restart_required = False
self._dialog = None self._dialog = None
self._restartDialog = None
self._download_progress = 0 self._download_progress = 0
self._is_downloading = False 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 # Installed plugins are really installed after reboot. In order to
# same file over and over again, we keep track of the upgraded plugins. # 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_installed_plugin_ids = []
self._newly_uninstalled_plugin_ids = []
self._plugin_statuses = {} # type: Dict[str, str]
# variables for the license agreement dialog # variables for the license agreement dialog
self._license_dialog_plugin_name = "" self._license_dialog_plugin_name = ""
self._license_dialog_license_content = "" self._license_dialog_license_content = ""
self._license_dialog_plugin_file_location = "" self._license_dialog_plugin_file_location = ""
self._restart_dialog_message = ""
showLicenseDialog = pyqtSignal() showLicenseDialog = pyqtSignal()
showRestartDialog = pyqtSignal()
pluginsMetadataChanged = pyqtSignal()
onDownloadProgressChanged = pyqtSignal()
onIsDownloadingChanged = pyqtSignal()
restartRequiredChanged = pyqtSignal()
viewChanged = pyqtSignal()
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getLicenseDialogPluginName(self): def getLicenseDialogPluginName(self):
@ -75,15 +100,19 @@ class PluginBrowser(QObject, Extension):
def getLicenseDialogLicenseContent(self): def getLicenseDialogLicenseContent(self):
return self._license_dialog_license_content 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): def openLicenseDialog(self, plugin_name, license_content, plugin_file_location):
self._license_dialog_plugin_name = plugin_name self._license_dialog_plugin_name = plugin_name
self._license_dialog_license_content = license_content self._license_dialog_license_content = license_content
self._license_dialog_plugin_file_location = plugin_file_location self._license_dialog_plugin_file_location = plugin_file_location
self.showLicenseDialog.emit() self.showLicenseDialog.emit()
pluginsMetadataChanged = pyqtSignal() def openRestartDialog(self, message):
onDownloadProgressChanged = pyqtSignal() self._restart_dialog_message = message
onIsDownloadingChanged = pyqtSignal() self.showRestartDialog.emit()
@pyqtProperty(bool, notify = onIsDownloadingChanged) @pyqtProperty(bool, notify = onIsDownloadingChanged)
def isDownloading(self): def isDownloading(self):
@ -179,17 +208,46 @@ class PluginBrowser(QObject, Extension):
@pyqtSlot(str) @pyqtSlot(str)
def installPlugin(self, file_path): def installPlugin(self, file_path):
# Ensure that it starts with a /, as otherwise it doesn't work on windows.
if not file_path.startswith("/"): 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: else:
location = file_path location = file_path
result = PluginRegistry.getInstance().installPlugin("file://" + location) result = PluginRegistry.getInstance().installPlugin("file://" + location)
self._newly_installed_plugin_ids.append(result["id"]) self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit() 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"]) 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) @pyqtProperty(int, notify = onDownloadProgressChanged)
def downloadProgress(self): def downloadProgress(self):
return self._download_progress return self._download_progress
@ -221,55 +279,72 @@ class PluginBrowser(QObject, Extension):
self.setDownloadProgress(0) self.setDownloadProgress(0)
self.setIsDownloading(False) self.setIsDownloading(False)
@pyqtSlot(str)
def setView(self, view):
self._view = view
self.viewChanged.emit()
self.pluginsMetadataChanged.emit()
@pyqtProperty(QObject, notify=pluginsMetadataChanged) @pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self): def pluginsModel(self):
if self._plugins_model is None: print("Updating plugins model...", self._view)
self._plugins_model = ListModel() self._plugins_model = PluginsModel(self._view)
self._plugins_model.addRoleName(Qt.UserRole + 1, "name") # self._plugins_model.update()
self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") # Check each plugin the registry for matching plugin from server
self._plugins_model.addRoleName(Qt.UserRole + 4, "author") # metadata, and if found, compare the versions. Higher version sets
self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") # 'can_upgrade' to 'True':
self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") for plugin in self._plugins_model.items:
self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade") if self._checkCanUpgrade(plugin["id"], plugin["version"]):
else: plugin["can_upgrade"] = True
self._plugins_model.clear() print(self._plugins_metadata)
items = []
for metadata in self._plugins_metadata: for item in self._plugins_metadata:
items.append({ if item["id"] == plugin["id"]:
"name": metadata["label"], plugin["update_url"] = item["file_location"]
"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)
return self._plugins_model return self._plugins_model
def _checkCanUpgrade(self, id, version): def _checkCanUpgrade(self, id, version):
plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id) # TODO: This could maybe be done more efficiently using a dictionary...
if metadata != {}:
if id in self._newly_installed_plugin_ids: # Scan plugin server data for plugin with the given id:
return False # We already updated this plugin. for plugin in self._plugins_metadata:
current_version = Version(metadata["plugin"]["version"]) if id == plugin["id"]:
new_version = Version(version) reg_version = Version(version)
if new_version > current_version: new_version = Version(plugin["version"])
return True if new_version > reg_version:
Logger.log("i", "%s has an update availible: %s", plugin["id"], plugin["version"])
return True
return False return False
def _checkAlreadyInstalled(self, id): def _checkAlreadyInstalled(self, id):
plugin_registry = PluginRegistry.getInstance() metadata = self._plugin_registry.getMetaData(id)
metadata = plugin_registry.getMetaData(id) # We already installed this plugin, but the registry just doesn't know it yet.
if metadata != {}: 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 return True
else: 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 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): def _onRequestFinished(self, reply):
reply_url = reply.url().toString() reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError: if reply.error() == QNetworkReply.TimeoutError:
@ -290,7 +365,11 @@ class PluginBrowser(QObject, Extension):
if reply_url == self._api_url + "plugins": if reply_url == self._api_url + "plugins":
try: try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Add metadata to the manager:
self._plugins_metadata = json_data self._plugins_metadata = json_data
print(self._plugins_metadata)
self._plugin_registry.addExternalPlugins(self._plugins_metadata)
self.pluginsMetadataChanged.emit() self.pluginsMetadataChanged.emit()
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.") 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 = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) 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()

View file

@ -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 2.2
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2 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 id: base
title: catalog.i18nc("@title:window", "Find & Update plugins") title: catalog.i18nc("@title:tab", "Plugins");
width: 600 * screenScaleFactor width: 800 * screenScaleFactor
height: 450 * screenScaleFactor height: 640 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor minimumHeight: 350 * screenScaleFactor
Item color: UM.Theme.getColor("sidebar")
{
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
}
Button Item {
{ id: view
id: refresh anchors {
text: catalog.i18nc("@action:button", "Refresh") fill: parent
onClicked: manager.requestPluginList() leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right rightMargin: UM.Theme.getSize("default_margin").width
enabled: !manager.isDownloading 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 width: parent.width
anchors.top: topBar.bottom height: 400
anchors.bottom: bottomBar.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height anchors {
top: topBar.bottom
topMargin: UM.Theme.getSize("default_margin").height
bottom: bottomBar.top
bottomMargin: UM.Theme.getSize("default_margin").height
}
frameVisible: true frameVisible: true
ListView
{ ListView {
id: pluginList id: pluginList
model: manager.pluginsModel property var activePlugin
property var filter: "installed"
anchors.fill: parent anchors.fill: parent
property var activePlugin model: manager.pluginsModel
delegate: pluginDelegate delegate: PluginEntry {}
} }
} }
Item
{ Rectangle {
id: bottomBar id: bottomBar
width: parent.width width: parent.width
height: closeButton.height height: childrenRect.height
color: "transparent"
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left
ProgressBar Label {
{ visible: manager.restartRequired
id: progressbar text: "You will need to restart Cura before changes in plugins have effect."
anchors.bottom: parent.bottom height: 30
minimumValue: 0; verticalAlignment: Text.AlignVCenter
maximumValue: 100 }
anchors.left:parent.left Button {
id: restartChangedButton
text: "Quit Cura"
anchors.right: closeButton.left anchors.right: closeButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width 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 id: closeButton
text: catalog.i18nc("@action:button", "Close") text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close" iconName: "dialog-close"
onClicked: onClicked: {
{ if ( manager.isDownloading ) {
if (manager.isDownloading)
{
manager.cancelDownload() manager.cancelDownload()
} }
base.close(); base.close();
} }
anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
} style: ButtonStyle {
} background: Rectangle {
color: "transparent"
Item implicitWidth: 96
{ implicitHeight: 30
SystemPalette { id: palette } border {
Component width: 1
{ color: UM.Theme.getColor("lining")
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: "<b>" + model.name + "</b>" + ((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
} }
} }
Button label: Text {
{ verticalAlignment: Text.AlignVCenter
id: downloadButton color: UM.Theme.getColor("text")
text: text: control.text
{ horizontalAlignment: Text.AlignHCenter
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);
}
}
} }
} }
} }
} }
UM.I18nCatalog { id: catalog; name: "cura" } UM.I18nCatalog { id: catalog; name: "cura" }
Connections Connections {
{
target: manager target: manager
onShowLicenseDialog: onShowLicenseDialog: {
{
licenseDialog.pluginName = manager.getLicenseDialogPluginName(); licenseDialog.pluginName = manager.getLicenseDialogPluginName();
licenseDialog.licenseContent = manager.getLicenseDialogLicenseContent(); licenseDialog.licenseContent = manager.getLicenseDialogLicenseContent();
licenseDialog.pluginFileLocation = manager.getLicenseDialogPluginFileLocation(); licenseDialog.pluginFileLocation = manager.getLicenseDialogPluginFileLocation();
@ -193,8 +211,7 @@ UM.Dialog
} }
} }
UM.Dialog UM.Dialog {
{
id: licenseDialog id: licenseDialog
title: catalog.i18nc("@title:window", "Plugin License Agreement") 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
}
}
}
}
} }
} }

View file

@ -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: "<a href=\"mailto:"+model.author_email+"?Subject=Cura: "+model.name+"\">"+model.author+"</a>"
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")
}
}
}
}
}
}