Plugin browswer updates

- Partial refactor
- Preparation for merge with plugins in preferences
This commit is contained in:
Ian Paschal 2018-01-23 16:20:19 +01:00
parent f27dc4473e
commit dcb1ac5deb
2 changed files with 205 additions and 45 deletions

View file

@ -1,17 +1,20 @@
# 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
@ -21,6 +24,39 @@ import shutil
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
# Architecture thoughts:
# ------------------------------------------------------------------------------
# The plugin manager has 2 parts: the browser and the installer.
#
# UNINSTALLING:
# ------------------------------------------------------------------------------
# Uninstalling is done via the PluginRegistry's uninstallPlugin() method,
# supplied with a plugin_id. Uninstalling is currently done instantly so there's
# no need to use an list of "newly uninstalled plugins" but this will be needed
# in the future with more complex plugins, as well as when merging the built-in
# plugins into the plugin browser.
#
# STATUS:
# ------------------------------------------------------------------------------
# 'status' is used to keep track of installations and uninstallations. It is
# intended to replace "newly installed" and "newly uninstalled" lists. These
# lists introduce a lot of complexity if a plugin finds itself on both lists.
#
# 'status' can have several values:
# - "uninstalled"
# - "will_install"
# - "installed"
# - "will_uninstall"
#
# This is more civilized than checking if a plugin's metadata exists. It also
# means uninstalling doesn't require erasing the metadata.
#
# ACTIVE:
# ------------------------------------------------------------------------------
# Although 'active' could have been lumped with 'status', it is essentially a
# sub-status within 'installed' and 'will_uninstall'. Rather than create more
# statuses such as 'active_installed' and 'active_will_uninstall', the 'active'
# property is just a boolean used in conjunction with 'status'.
class PluginBrowser(QObject, Extension): class PluginBrowser(QObject, Extension):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -35,11 +71,13 @@ 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
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
@ -53,16 +91,27 @@ 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()
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getLicenseDialogPluginName(self): def getLicenseDialogPluginName(self):
@ -76,15 +125,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):
@ -191,12 +244,14 @@ class PluginBrowser(QObject, Extension):
self._newly_installed_plugin_ids.append(result["id"]) self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit() self.pluginsMetadataChanged.emit()
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"]) self.openRestartDialog(result["message"])
# Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
@pyqtSlot(str) @pyqtSlot(str)
def removePlugin(self, plugin_id): def removePlugin(self, plugin_id):
result = PluginRegistry.getInstance().uninstallPlugin(plugin_id) result = PluginRegistry.getInstance().uninstallPlugin(plugin_id)
self._newly_uninstalled_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit() self.pluginsMetadataChanged.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"])
@ -233,19 +288,12 @@ class PluginBrowser(QObject, Extension):
self.setIsDownloading(False) self.setIsDownloading(False)
@pyqtProperty(QObject, notify=pluginsMetadataChanged) @pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self): def pluginsModel(self):
self._plugins_model = PluginsModel()
"""
if self._plugins_model is None: if self._plugins_model is None:
self._plugins_model = ListModel() self._plugins_model = PluginsModel()
self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
self._plugins_model.addRoleName(Qt.UserRole + 2, "id")
self._plugins_model.addRoleName(Qt.UserRole + 3, "version")
self._plugins_model.addRoleName(Qt.UserRole + 4, "short_description")
self._plugins_model.addRoleName(Qt.UserRole + 5, "author")
self._plugins_model.addRoleName(Qt.UserRole + 6, "author_email")
self._plugins_model.addRoleName(Qt.UserRole + 7, "already_installed")
self._plugins_model.addRoleName(Qt.UserRole + 8, "file_location")
self._plugins_model.addRoleName(Qt.UserRole + 9, "enabled")
self._plugins_model.addRoleName(Qt.UserRole + 10, "can_upgrade")
else: else:
self._plugins_model.clear() self._plugins_model.clear()
items = [] items = []
@ -257,16 +305,19 @@ class PluginBrowser(QObject, Extension):
"short_description": metadata["short_description"], "short_description": metadata["short_description"],
"author": metadata["author"], "author": metadata["author"],
"author_email": "author@gmail.com", "author_email": "author@gmail.com",
"status": self._checkInstallStatus(metadata["id"]),
"already_installed": self._checkAlreadyInstalled(metadata["id"]), "already_installed": self._checkAlreadyInstalled(metadata["id"]),
"file_location": metadata["file_location"], "file_location": metadata["file_location"],
# "enabled": self._checkEnabled(metadata["id"]), # "active": self._checkActive(metadata["id"]),
"enabled": True, "enabled": True,
"can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"]) "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"])
}) })
self._plugins_model.setItems(items) 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 = Application.getInstance().getPluginRegistry()
plugin_registry = PluginRegistry.getInstance() plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id) metadata = plugin_registry.getMetaData(id)
if metadata != {}: if metadata != {}:
@ -281,13 +332,26 @@ class PluginBrowser(QObject, Extension):
def _checkAlreadyInstalled(self, id): def _checkAlreadyInstalled(self, id):
plugin_registry = PluginRegistry.getInstance() plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id) metadata = plugin_registry.getMetaData(id)
if metadata != {}: # 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 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):
plugin_registry = PluginRegistry.getInstance()
# If plugin is registered, it's installed:
if plugin_id in plugin_registry._plugins:
return "installed"
else:
return "uninstalled"
def _checkEnabled(self, id): def _checkEnabled(self, id):
plugin_registry = PluginRegistry.getInstance() plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id) metadata = plugin_registry.getMetaData(id)

View file

@ -1,4 +1,6 @@
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
@ -7,18 +9,34 @@ import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles // TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM
UM.Dialog { UM.Dialog {
id: base id: base
title: catalog.i18nc("@title:tab", "Plugins"); title: catalog.i18nc("@title:tab", "Plugins");
width: 800 * screenScaleFactor width: 800 * screenScaleFactor
height: 600 * screenScaleFactor height: 640 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor minimumHeight: 350 * screenScaleFactor
Item { Column {
anchors.fill: parent // anchors.fill: parent
height: parent.height
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
Rectangle {
id: topBar
width: parent.width
color: "red"
height: 30
Text {
text: "Search"
}
}
/*
Item { Item {
id: topBar id: topBar
height: childrenRect.height; height: childrenRect.height;
@ -39,15 +57,24 @@ UM.Dialog {
enabled: !manager.isDownloading enabled: !manager.isDownloading
} }
} }
*/
// Scroll view breaks in QtQuick.Controls 2.x // Scroll view breaks in QtQuick.Controls 2.x
Label {
text: "Installed Plugins"
}
ScrollView { ScrollView {
id: installedPluginList
width: parent.width width: parent.width
height: 280
/*
anchors.top: topBar.bottom anchors.top: topBar.bottom
anchors.bottom: bottomBar.top anchors.bottom: availiblePluginList.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height anchors.bottomMargin: UM.Theme.getSize("default_margin").height
*/
frameVisible: true frameVisible: true
ListView { ListView {
id: pluginList id: pluginList
model: manager.pluginsModel model: manager.pluginsModel
@ -56,13 +83,45 @@ UM.Dialog {
delegate: pluginDelegate delegate: pluginDelegate
} }
} }
Label {
text: "Availible plugins..."
}
/*
Rectangle {
width: parent.width
color: "red"
height: 200
Text {
text: "Plugins not installed yet"
}
}
*/
Item { // Scroll view breaks in QtQuick.Controls 2.x
ScrollView {
id: availiblePluginList
width: parent.width
/*
anchors.top: installedPluginList.bottom
anchors.bottom: bottomBar.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
*/
frameVisible: true
height: 180
Rectangle {
width: parent.width
color: "red"
height: 1000
}
}
Rectangle {
id: bottomBar id: bottomBar
width: parent.width width: parent.width
height: closeButton.height height: childrenRect.height;
anchors.bottom: parent.bottom // anchors.bottom: parent.bottom
anchors.left: parent.left // anchors.left: parent.left
Button { Button {
id: closeButton id: closeButton
@ -79,7 +138,7 @@ UM.Dialog {
} }
} }
Item { Rectangle {
Component { Component {
id: pluginDelegate id: pluginDelegate
@ -109,7 +168,8 @@ UM.Dialog {
pixelSize: 13 pixelSize: 13
bold: true bold: true
} }
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("secondary") // color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("secondary")
color: UM.Theme.getColor("text")
} }
Label { Label {
@ -367,11 +427,9 @@ UM.Dialog {
} }
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();
@ -379,8 +437,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")
@ -444,5 +501,44 @@ UM.Dialog {
} }
] ]
} }
Connections {
target: manager
onShowRestartDialog: {
restartDialog.message = manager.getRestartDialogMessage();
restartDialog.show();
}
}
UM.Dialog {
id: restartDialog
// title: catalog.i18nc("@title:tab", "Plugins");
width: 360 * screenScaleFactor
height: 180 * screenScaleFactor
minimumWidth: 360 * screenScaleFactor
minimumHeight: 180 * screenScaleFactor
property var message;
Text {
id: message
text: restartDialog.message != null ? restartDialog.message : ""
}
Button {
id: laterButton
text: "Later"
onClicked: restartDialog.close();
anchors.left: parent.left
anchors.bottom: parent.bottom
}
Button {
id: restartButton
text: "Restart now"
onClicked: restartDialog.close();
anchors.right: parent.right
anchors.bottom: parent.bottom
}
}
} }
} }