Merge pull request #4945 from Ultimaker/CURA-6006-login-required

[4.0] CURA-6006 login required
This commit is contained in:
alekseisasin 2018-12-12 11:31:38 +01:00 committed by GitHub
commit a505adbf40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 382 additions and 425 deletions

View File

@ -81,9 +81,14 @@ class AuthorizationHelpers:
# \param access_token: The encoded JWT token. # \param access_token: The encoded JWT token.
# \return: Dict containing some profile data. # \return: Dict containing some profile data.
def parseJWT(self, access_token: str) -> Optional["UserProfile"]: def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
try:
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = { token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
"Authorization": "Bearer {}".format(access_token) "Authorization": "Bearer {}".format(access_token)
}) })
except ConnectionError:
# Connection was suddenly dropped. Nothing we can do about that.
Logger.logException("e", "Something failed while attempting to parse the JWT token")
return None
if token_request.status_code not in (200, 201): if token_request.status_code not in (200, 201):
Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text) Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text)
return None return None

View File

@ -14,8 +14,8 @@ Window
modality: Qt.ApplicationModal modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
width: 720 * screenScaleFactor width: Math.floor(720 * screenScaleFactor)
height: 640 * screenScaleFactor height: Math.floor(640 * screenScaleFactor)
minimumWidth: width minimumWidth: width
maximumWidth: minimumWidth maximumWidth: minimumWidth
minimumHeight: height minimumHeight: height
@ -95,6 +95,7 @@ Window
licenseDialog.show(); licenseDialog.show();
} }
} }
ToolboxLicenseDialog ToolboxLicenseDialog
{ {
id: licenseDialog id: licenseDialog

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -59,6 +59,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: UM.Theme.getSize("toolbox_property_label").height
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -70,6 +71,7 @@ Item
left: title.left left: title.left
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
} }
renderType: Text.NativeRendering
} }
Column Column
{ {
@ -88,12 +90,14 @@ Item
text: catalog.i18nc("@label", "Website") + ":" text: catalog.i18nc("@label", "Website") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
Label Label
{ {
text: catalog.i18nc("@label", "Email") + ":" text: catalog.i18nc("@label", "Email") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
} }
Column Column
@ -122,6 +126,7 @@ Item
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
} }
Label Label
@ -138,6 +143,7 @@ Item
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
} }
} }
Rectangle Rectangle

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -64,6 +64,7 @@ Item
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
width: control.width width: control.width
renderType: Text.NativeRendering
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -67,6 +67,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
} }
TableView TableView
@ -99,6 +100,7 @@ Item
text: styleData.value || "" text: styleData.value || ""
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
} }
Rectangle Rectangle
{ {
@ -118,6 +120,7 @@ Item
text: styleData.value || "" text: styleData.value || ""
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }
itemDelegate: Item itemDelegate: Item
@ -130,6 +133,7 @@ Item
text: styleData.value || "" text: styleData.value || ""
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }
@ -144,6 +148,7 @@ Item
elide: Text.ElideRight elide: Text.ElideRight
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }
@ -232,5 +237,6 @@ Item
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.10
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
@ -66,6 +66,7 @@ UM.Dialog
anchors.right: parent.right anchors.right: parent.right
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
renderType: Text.NativeRendering
} }
// Buttons // Buttons

View File

@ -26,10 +26,19 @@ Item
} }
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
Repeater Repeater
{ {
model: toolbox.packagesModel model: toolbox.packagesModel
delegate: ToolboxDetailTile {} delegate: Loader
{
// FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
// leaving an empty space below the title part. We turn it off for now to make it work on Mac and
// Windows.
// Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
asynchronous: false
source: "ToolboxDetailTile.qml"
}
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -65,6 +65,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: UM.Theme.getSize("toolbox_property_label").height
renderType: Text.NativeRendering
} }
Column Column
@ -84,24 +85,28 @@ Item
text: catalog.i18nc("@label", "Version") + ":" text: catalog.i18nc("@label", "Version") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
Label Label
{ {
text: catalog.i18nc("@label", "Last updated") + ":" text: catalog.i18nc("@label", "Last updated") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
Label Label
{ {
text: catalog.i18nc("@label", "Author") + ":" text: catalog.i18nc("@label", "Author") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
Label Label
{ {
text: catalog.i18nc("@label", "Downloads") + ":" text: catalog.i18nc("@label", "Downloads") + ":"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
} }
} }
Column Column
@ -121,6 +126,7 @@ Item
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown")) text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -135,6 +141,7 @@ Item
} }
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -153,12 +160,14 @@ Item
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
} }
Label Label
{ {
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown")) text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
} }
} }
Rectangle Rectangle

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -31,6 +31,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold") font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -42,6 +43,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }

View File

@ -1,40 +1,69 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.1 as Cura
Column Column
{ {
property bool installed: toolbox.isInstalled(model.id) property bool installed: toolbox.isInstalled(model.id)
property bool canUpdate: toolbox.canUpdate(model.id) property bool canUpdate: toolbox.canUpdate(model.id)
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height spacing: UM.Theme.getSize("narrow_margin").height
Item
{
width: installButton.width
height: installButton.height
ToolboxProgressButton ToolboxProgressButton
{ {
id: installButton id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model active: toolbox.isDownloading && toolbox.activePackage == model
complete: installed onReadyAction:
readyAction: function()
{ {
toolbox.activePackage = model toolbox.activePackage = model
toolbox.startDownload(model.download_url) toolbox.startDownload(model.download_url)
} }
activeAction: function() onActiveAction: toolbox.cancelDownload()
{
toolbox.cancelDownload()
}
completeAction: function()
{
toolbox.viewCategory = "installed"
}
// Don't allow installing while another download is running // Don't allow installing while another download is running
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible // Don't show when the update button is visible visible: !updateButton.visible && !installed// Don't show when the update button is visible
}
Cura.SecondaryButton
{
visible: installed
onClicked: toolbox.viewCategory = "installed"
text: catalog.i18nc("@action:button", "Installed")
fixedWidthMode: true
width: installButton.width
height: installButton.height
}
}
Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to install or update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: installButton.width
renderType: Text.NativeRendering
MouseArea
{
anchors.fill: parent
onClicked: Cura.API.account.login()
}
} }
ToolboxProgressButton ToolboxProgressButton
@ -44,20 +73,19 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update") readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating") activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated") completeLabel: catalog.i18nc("@action:button", "Updated")
readyAction: function()
onReadyAction:
{ {
toolbox.activePackage = model toolbox.activePackage = model
toolbox.update(model.id) toolbox.update(model.id)
} }
activeAction: function() onActiveAction: toolbox.cancelDownload()
{
toolbox.cancelDownload()
}
// Don't allow installing while another download is running // Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1.0 : 0.5
visible: canUpdate visible: canUpdate
} }
Connections Connections
{ {
target: toolbox target: toolbox

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -23,8 +23,9 @@ Column
width: parent.width width: parent.width
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
} }
GridLayout Grid
{ {
id: grid id: grid
width: parent.width - 2 * parent.padding width: parent.width - 2 * parent.padding
@ -34,10 +35,12 @@ Column
Repeater Repeater
{ {
model: gridArea.model model: gridArea.model
delegate: ToolboxDownloadsGridTile delegate: Loader
{ {
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns asynchronous: true
Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
height: UM.Theme.getSize("toolbox_thumbnail_small").height
source: "ToolboxDownloadsGridTile.qml"
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -62,6 +62,8 @@ Item
{ {
width: parent.width - thumbnail.width - parent.spacing width: parent.width - thumbnail.width - parent.spacing
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width) spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
Label Label
{ {
id: name id: name
@ -70,6 +72,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -81,6 +84,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -24,19 +24,19 @@ Rectangle
width: parent.width width: parent.width
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
} }
Grid Grid
{ {
height: childrenRect.height height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width spacing: UM.Theme.getSize("wide_margin").width
columns: 3 columns: 3
anchors anchors.horizontalCenter: parent.horizontalCenter
{
horizontalCenter: parent.horizontalCenter
}
Repeater Repeater
{ {
model: { model:
{
if (toolbox.viewCategory == "plugin") if (toolbox.viewCategory == "plugin")
{ {
return toolbox.pluginsShowcaseModel return toolbox.pluginsShowcaseModel
@ -46,7 +46,11 @@ Rectangle
return toolbox.materialsShowcaseModel return toolbox.materialsShowcaseModel
} }
} }
delegate: ToolboxDownloadsShowcaseTile {} delegate: Loader
{
asynchronous: true
source: "ToolboxDownloadsShowcaseTile.qml"
}
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
@ -79,6 +79,7 @@ Rectangle
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("button_text") color: UM.Theme.getColor("button_text")
font: UM.Theme.getFont("medium_bold") font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
} }
} }
MouseArea MouseArea

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
@ -18,5 +18,6 @@ Rectangle
{ {
centerIn: parent centerIn: parent
} }
renderType: Text.NativeRendering
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -26,7 +26,7 @@ Item
right: restartButton.right right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width rightMargin: UM.Theme.getSize("default_margin").width
} }
renderType: Text.NativeRendering
} }
Button Button
{ {
@ -56,6 +56,7 @@ Item
text: control.text text: control.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
@ -21,44 +21,40 @@ ScrollView
Column Column
{ {
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
visible: toolbox.pluginsInstalledModel.items.length > 0
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
anchors anchors
{ {
right: parent.right right: parent.right
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width margins: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
bottomMargin: UM.Theme.getSize("wide_margin").height
top: parent.top top: parent.top
} }
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
Label Label
{ {
visible: toolbox.pluginsInstalledModel.items.length > 0 width: page.width
width: parent.width
text: catalog.i18nc("@title:tab", "Plugins") text: catalog.i18nc("@title:tab", "Plugins")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
} }
Rectangle Rectangle
{ {
visible: toolbox.pluginsInstalledModel.items.length > 0
color: "transparent" color: "transparent"
width: parent.width width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining") border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
Column Column
{ {
height: childrenRect.height
anchors anchors
{ {
top: parent.top top: parent.top
right: parent.right right: parent.right
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width margins: 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 Repeater
{ {
@ -70,32 +66,27 @@ ScrollView
} }
Label Label
{ {
visible: toolbox.materialsInstalledModel.items.length > 0
width: page.width
text: catalog.i18nc("@title:tab", "Materials") text: catalog.i18nc("@title:tab", "Materials")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
} }
Rectangle Rectangle
{ {
visible: toolbox.materialsInstalledModel.items.length > 0
color: "transparent" color: "transparent"
width: parent.width width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining") border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
Column Column
{ {
height: Math.max( UM.Theme.getSize("wide_margin").height, childrenRect.height)
anchors anchors
{ {
top: parent.top top: parent.top
right: parent.right right: parent.right
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width margins: 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 Repeater
{ {

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
@ -51,6 +51,7 @@ Item
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
color: pluginInfo.color color: pluginInfo.color
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -60,6 +61,7 @@ Item
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: pluginInfo.color color: pluginInfo.color
renderType: Text.NativeRendering
} }
} }
Column Column
@ -88,6 +90,7 @@ Item
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin") onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining") color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")
renderType: Text.NativeRendering
} }
Label Label
@ -98,6 +101,7 @@ Item
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
renderType: Text.NativeRendering
} }
} }
ToolboxInstalledTileActions ToolboxInstalledTileActions

View File

@ -1,15 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.1 as Cura
Column Column
{ {
property bool canUpdate: false property bool canUpdate: false
property bool canDowngrade: false property bool canDowngrade: false
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height spacing: UM.Theme.getSize("narrow_margin").height
@ -21,6 +24,7 @@ Column
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
renderType: Text.NativeRendering
} }
ToolboxProgressButton ToolboxProgressButton
@ -30,59 +34,49 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update") readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating") activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated") completeLabel: catalog.i18nc("@action:button", "Updated")
readyAction: function() onReadyAction:
{ {
toolbox.activePackage = model toolbox.activePackage = model
toolbox.update(model.id) toolbox.update(model.id)
} }
activeAction: function() onActiveAction: toolbox.cancelDownload()
{
toolbox.cancelDownload()
}
// Don't allow installing while another download is running // Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1.0 : 0.5
visible: canUpdate visible: canUpdate
} }
Button Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: updateButton.width
renderType: Text.NativeRendering
MouseArea
{
anchors.fill: parent
onClicked: Cura.API.account.login()
}
}
Cura.SecondaryButton
{ {
id: removeButton id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall") text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled && model.is_installed visible: !model.is_bundled && model.is_installed
enabled: !toolbox.isDownloading enabled: !toolbox.isDownloading
style: ButtonStyle
{ width: UM.Theme.getSize("toolbox_action_button").width
background: Rectangle height: UM.Theme.getSize("toolbox_action_button").height
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width fixedWidthMode: true
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color: "transparent"
border
{
width: UM.Theme.getSize("default_lining").width
color:
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("lining")
}
}
}
}
label: Label
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font: UM.Theme.getFont("default")
}
}
onClicked: toolbox.checkPackageUsageAndUninstall(model.id) onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
Connections Connections
{ {

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.10
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
@ -32,6 +32,7 @@ UM.Dialog
anchors.right: parent.right anchors.right: parent.right
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?") text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
wrapMode: Text.Wrap wrapMode: Text.Wrap
renderType: Text.NativeRendering
} }
TextArea TextArea
{ {

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
@ -18,5 +18,6 @@ Rectangle
{ {
centerIn: parent centerIn: parent
} }
renderType: Text.NativeRendering
} }
} }

View File

@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.0 as Cura
Item Item
@ -18,16 +19,19 @@ Item
property var activeLabel: catalog.i18nc("@action:button", "Cancel") property var activeLabel: catalog.i18nc("@action:button", "Cancel")
property var completeLabel: catalog.i18nc("@action:button", "Installed") property var completeLabel: catalog.i18nc("@action:button", "Installed")
property var readyAction: null // Action when button is ready and clicked (likely install) signal readyAction() // Action when button is ready and clicked (likely install)
property var activeAction: null // Action when button is active and clicked (likely cancel) signal activeAction() // Action when button is active and clicked (likely cancel)
property var completeAction: null // Action when button is complete and clicked (likely go to installed) signal completeAction() // Action when button is complete and clicked (likely go to installed)
width: UM.Theme.getSize("toolbox_action_button").width width: UM.Theme.getSize("toolbox_action_button").width
height: UM.Theme.getSize("toolbox_action_button").height height: UM.Theme.getSize("toolbox_action_button").height
Button Cura.PrimaryButton
{ {
id: button id: button
width: UM.Theme.getSize("toolbox_action_button").width
height: UM.Theme.getSize("toolbox_action_button").height
fixedWidthMode: true
text: text:
{ {
if (complete) if (complete)
@ -47,101 +51,15 @@ Item
{ {
if (complete) if (complete)
{ {
return completeAction() completeAction()
} }
else if (active) else if (active)
{ {
return activeAction() activeAction()
} }
else else
{ {
return readyAction() readyAction()
}
}
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color:
{
if (base.complete)
{
return "transparent"
}
else
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("primary")
}
}
}
border
{
width:
{
if (base.complete)
{
UM.Theme.getSize("default_lining").width
}
else
{
return 0
}
}
color:
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("lining")
}
}
}
}
label: Label
{
text: control.text
color:
{
if (base.complete)
{
return UM.Theme.getColor("text")
}
else
{
if (control.hovered)
{
return UM.Theme.getColor("button_text_hover")
}
else
{
return UM.Theme.getColor("button_text")
}
}
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font:
{
if (base.complete)
{
return UM.Theme.getFont("default")
}
else
{
return UM.Theme.getFont("default_bold")
}
}
} }
} }
} }

View File

@ -1,32 +1,32 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher. // Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM import UM 1.1 as UM
Button Button
{ {
id: control
property bool active: false property bool active: false
style: ButtonStyle hoverEnabled: true
background: Item
{ {
background: Rectangle
{
color: "transparent"
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
Rectangle Rectangle
{ {
visible: control.active visible: control.active
color: UM.Theme.getColor("toolbox_header_highlight_hover") color: UM.Theme.getColor("primary")
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_header_highlight").height height: UM.Theme.getSize("toolbox_header_highlight").height
} }
} }
label: Label contentItem: Label
{ {
id: label
text: control.text text: control.text
color: color:
{ {
@ -46,6 +46,6 @@ Button
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic") font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} renderType: Text.NativeRendering
} }
} }

View File

@ -2,18 +2,19 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import re import re
from typing import Dict from typing import Dict, List, Optional, Union
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed. ## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
class AuthorsModel(ListModel): class AuthorsModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
self._metadata = None self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]]
self.addRoleName(Qt.UserRole + 1, "id") self.addRoleName(Qt.UserRole + 1, "id")
self.addRoleName(Qt.UserRole + 2, "name") self.addRoleName(Qt.UserRole + 2, "name")
@ -27,37 +28,38 @@ class AuthorsModel(ListModel):
# List of filters for queries. The result is the union of the each list of results. # List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str] self._filter = {} # type: Dict[str, str]
def setMetadata(self, data): def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]):
if self._metadata != data:
self._metadata = data self._metadata = data
self._update() self._update()
def _update(self): def _update(self) -> None:
items = [] items = [] # type: List[Dict[str, Union[str, List[str], int, None]]]
if not self._metadata: if not self._metadata:
self.setItems([]) self.setItems(items)
return return
for author in self._metadata: for author in self._metadata:
items.append({ items.append({
"id": author["author_id"], "id": author.get("author_id"),
"name": author["display_name"], "name": author.get("display_name"),
"email": author["email"] if "email" in author else None, "email": author.get("email"),
"website": author["website"], "website": author.get("website"),
"package_count": author["package_count"] if "package_count" in author else 0, "package_count": author.get("package_count", 0),
"package_types": author["package_types"] if "package_types" in author else [], "package_types": author.get("package_types", []),
"icon_url": author["icon_url"] if "icon_url" in author else None, "icon_url": author.get("icon_url"),
"description": "Material and quality profiles from {author_name}".format(author_name = author["display_name"]) "description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", ""))
}) })
# Filter on all the key-word arguments. # Filter on all the key-word arguments.
for key, value in self._filter.items(): for key, value in self._filter.items():
if key is "package_types": if key is "package_types":
key_filter = lambda item, value = value: value in item["package_types"] key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
elif "*" in value: elif "*" in value:
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
else: else:
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore
items = filter(key_filter, items) items = filter(key_filter, items) # type: ignore
# Execute all filters. # Execute all filters.
filtered_items = list(items) filtered_items = list(items)

View File

@ -40,11 +40,13 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 19, "tags") self.addRoleName(Qt.UserRole + 19, "tags")
self.addRoleName(Qt.UserRole + 20, "links") self.addRoleName(Qt.UserRole + 20, "links")
self.addRoleName(Qt.UserRole + 21, "website") self.addRoleName(Qt.UserRole + 21, "website")
self.addRoleName(Qt.UserRole + 22, "login_required")
# List of filters for queries. The result is the union of the each list of results. # List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str] self._filter = {} # type: Dict[str, str]
def setMetadata(self, data): def setMetadata(self, data):
if self._metadata != data:
self._metadata = data self._metadata = data
self._update() self._update()
@ -99,6 +101,7 @@ class PackagesModel(ListModel):
"tags": package["tags"] if "tags" in package else [], "tags": package["tags"] if "tags" in package else [],
"links": links_dict, "links": links_dict,
"website": package["website"] if "website" in package else None, "website": package["website"] if "website" in package else None,
"login_required": "login-required" in package.get("tags", [])
}) })
# Filter on all the key-word arguments. # Filter on all the key-word arguments.

View File

@ -66,31 +66,26 @@ class Toolbox(QObject, Extension):
self._old_plugin_ids = set() # type: Set[str] self._old_plugin_ids = set() # type: Set[str]
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
# Data: # The responses as given by the server parsed to a list.
self._metadata = { self._server_response_data = {
"authors": [], "authors": [],
"packages": [], "packages": []
"plugins_showcase": [],
"plugins_available": [],
"plugins_installed": [],
"materials_showcase": [],
"materials_available": [],
"materials_installed": [],
"materials_generic": []
} # type: Dict[str, List[Any]] } # type: Dict[str, List[Any]]
# Models: # Models:
self._models = { self._models = {
"authors": AuthorsModel(self), "authors": AuthorsModel(self),
"packages": PackagesModel(self), "packages": PackagesModel(self),
"plugins_showcase": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]]
"plugins_available": PackagesModel(self),
"plugins_installed": PackagesModel(self), self._plugins_showcase_model = PackagesModel(self)
"materials_showcase": AuthorsModel(self), self._plugins_available_model = PackagesModel(self)
"materials_available": AuthorsModel(self), self._plugins_installed_model = PackagesModel(self)
"materials_installed": PackagesModel(self),
"materials_generic": PackagesModel(self) self._materials_showcase_model = AuthorsModel(self)
} # type: Dict[str, ListModel] self._materials_available_model = AuthorsModel(self)
self._materials_installed_model = PackagesModel(self)
self._materials_generic_model = PackagesModel(self)
# These properties are for keeping track of the UI state: # These properties are for keeping track of the UI state:
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ -178,12 +173,7 @@ class Toolbox(QObject, Extension):
) )
self._request_urls = { self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)), "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)), "packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url = self._api_url)),
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url = self._api_url))
} }
# Get the API root for the packages API depending on Cura version settings. # Get the API root for the packages API depending on Cura version settings.
@ -231,12 +221,6 @@ class Toolbox(QObject, Extension):
# Make remote requests: # Make remote requests:
self._makeRequestByType("packages") self._makeRequestByType("packages")
self._makeRequestByType("authors") self._makeRequestByType("authors")
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
# self._makeRequestByType("plugins_showcase")
# self._makeRequestByType("plugins_available")
# self._makeRequestByType("materials_showcase")
# self._makeRequestByType("materials_available")
# self._makeRequestByType("materials_generic")
# Gather installed packages: # Gather installed packages:
self._updateInstalledModels() self._updateInstalledModels()
@ -281,7 +265,7 @@ class Toolbox(QObject, Extension):
"description": plugin_data["plugin"]["description"] "description": plugin_data["plugin"]["description"]
} }
return formatted return formatted
except: except KeyError:
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data)) Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
return None return None
@ -319,13 +303,10 @@ class Toolbox(QObject, Extension):
if plugin_id not in all_plugin_package_ids) if plugin_id not in all_plugin_package_ids)
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids} self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
self._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values()) self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self.metadataChanged.emit() self.metadataChanged.emit()
if "material" in all_packages: if "material" in all_packages:
self._metadata["materials_installed"] = all_packages["material"] self._materials_installed_model.setMetadata(all_packages["material"])
# TODO: ADD MATERIALS HERE ONCE MATERIALS PORTION OF TOOLBOX IS LIVE
self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
self.metadataChanged.emit() self.metadataChanged.emit()
@pyqtSlot(str) @pyqtSlot(str)
@ -479,7 +460,7 @@ class Toolbox(QObject, Extension):
def getRemotePackage(self, package_id: str) -> Optional[Dict]: def getRemotePackage(self, package_id: str) -> Optional[Dict]:
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
remote_package = None remote_package = None
for package in self._metadata["packages"]: for package in self._server_response_data["packages"]:
if package["package_id"] == package_id: if package["package_id"] == package_id:
remote_package = package remote_package = package
break break
@ -491,11 +472,8 @@ class Toolbox(QObject, Extension):
def canUpdate(self, package_id: str) -> bool: def canUpdate(self, package_id: str) -> bool:
local_package = self._package_manager.getInstalledPackageInfo(package_id) local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None: if local_package is None:
Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
package_id)
local_package = self.getOldPluginPackageMetadata(package_id) local_package = self.getOldPluginPackageMetadata(package_id)
if local_package is None: if local_package is None:
Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
return False return False
remote_package = self.getRemotePackage(package_id) remote_package = self.getRemotePackage(package_id)
@ -545,8 +523,8 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = int) @pyqtSlot(str, result = int)
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int: def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
count = 0 count = 0
for package in self._metadata["materials_installed"]: for package in self._materials_installed_model.items:
if package["author"]["author_id"] == author_id: if package["author_id"] == author_id:
count += 1 count += 1
return count return count
@ -554,7 +532,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = int) @pyqtSlot(str, result = int)
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int: def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
count = 0 count = 0
for package in self._metadata["packages"]: for package in self._server_response_data["packages"]:
if package["package_type"] == "material": if package["package_type"] == "material":
if package["author"]["author_id"] == author_id: if package["author"]["author_id"] == author_id:
count += 1 count += 1
@ -568,34 +546,30 @@ class Toolbox(QObject, Extension):
# Check for plugins that were installed with the old plugin browser # Check for plugins that were installed with the old plugin browser
def isOldPlugin(self, plugin_id: str) -> bool: def isOldPlugin(self, plugin_id: str) -> bool:
if plugin_id in self._old_plugin_ids: return plugin_id in self._old_plugin_ids
return True
return False
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]: def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
return self._old_plugin_metadata.get(plugin_id) return self._old_plugin_metadata.get(plugin_id)
def loadingComplete(self) -> bool: def isLoadingComplete(self) -> bool:
populated = 0 populated = 0
for list in self._metadata.items(): for metadata_list in self._server_response_data.items():
if len(list) > 0: if metadata_list:
populated += 1 populated += 1
if populated == len(self._metadata.items()): return populated == len(self._server_response_data.items())
return True
return False
# Make API Calls # Make API Calls
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
def _makeRequestByType(self, type: str) -> None: def _makeRequestByType(self, request_type: str) -> None:
Logger.log("i", "Marketplace: Requesting %s metadata from server.", type) Logger.log("i", "Requesting %s metadata from server.", request_type)
request = QNetworkRequest(self._request_urls[type]) request = QNetworkRequest(self._request_urls[request_type])
request.setRawHeader(*self._request_header) request.setRawHeader(*self._request_header)
if self._network_manager: if self._network_manager:
self._network_manager.get(request) self._network_manager.get(request)
@pyqtSlot(str) @pyqtSlot(str)
def startDownload(self, url: str) -> None: def startDownload(self, url: str) -> None:
Logger.log("i", "Marketplace: Attempting to download & install package from %s.", url) Logger.log("i", "Attempting to download & install package from %s.", url)
url = QUrl(url) url = QUrl(url)
self._download_request = QNetworkRequest(url) self._download_request = QNetworkRequest(url)
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"): if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
@ -612,7 +586,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot() @pyqtSlot()
def cancelDownload(self) -> None: def cancelDownload(self) -> None:
Logger.log("i", "Marketplace: User cancelled the download of a package.") Logger.log("i", "User cancelled the download of a package.")
self.resetDownload() self.resetDownload()
def resetDownload(self) -> None: def resetDownload(self) -> None:
@ -646,22 +620,8 @@ class Toolbox(QObject, Extension):
self.resetDownload() self.resetDownload()
return return
# HACK: These request are not handled independently at this moment, but together from the "packages" call
do_not_handle = [
"materials_available",
"materials_showcase",
"materials_generic",
"plugins_available",
"plugins_showcase",
]
if reply.operation() == QNetworkAccessManager.GetOperation: if reply.operation() == QNetworkAccessManager.GetOperation:
for type, url in self._request_urls.items(): for response_type, url in self._request_urls.items():
# HACK: Do nothing because we'll handle these from the "packages" call
if type in do_not_handle:
continue
if reply.url() == url: if reply.url() == url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
try: try:
@ -674,38 +634,32 @@ class Toolbox(QObject, Extension):
return return
# Create model and apply metadata: # Create model and apply metadata:
if not self._models[type]: if not self._models[response_type]:
Logger.log("e", "Could not find the %s model.", type) Logger.log("e", "Could not find the %s model.", response_type)
break break
self._metadata[type] = json_data["data"] self._server_response_data[response_type] = json_data["data"]
self._models[type].setMetadata(self._metadata[type]) self._models[response_type].setMetadata(self._server_response_data[response_type])
# Do some auto filtering if response_type is "packages":
# TODO: Make multiple API calls in the future to handle this self._models[response_type].setFilter({"type": "plugin"})
if type is "packages": self.reBuildMaterialsModels()
self._models[type].setFilter({"type": "plugin"}) self.reBuildPluginsModels()
self.buildMaterialsModels() elif response_type is "authors":
self.buildPluginsModels() self._models[response_type].setFilter({"package_types": "material"})
if type is "authors": self._models[response_type].setFilter({"tags": "generic"})
self._models[type].setFilter({"package_types": "material"})
if type is "materials_generic":
self._models[type].setFilter({"tags": "generic"})
self.metadataChanged.emit() self.metadataChanged.emit()
if self.loadingComplete() is True: if self.isLoadingComplete():
self.setViewPage("overview") self.setViewPage("overview")
return
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.log("w", "Marketplace: Received invalid JSON for %s.", type) Logger.log("w", "Received invalid JSON for %s.", response_type)
break break
else: else:
self.setViewPage("errored") self.setViewPage("errored")
self.resetDownload() self.resetDownload()
return
else: else:
# Ignore any operation that is not a get operation # Ignore any operation that is not a get operation
pass pass
@ -716,7 +670,13 @@ class Toolbox(QObject, Extension):
self.setDownloadProgress(new_progress) self.setDownloadProgress(new_progress)
if bytes_sent == bytes_total: if bytes_sent == bytes_total:
self.setIsDownloading(False) self.setIsDownloading(False)
cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress) self._download_reply = cast(QNetworkReply, self._download_reply)
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
# Check if the download was sucessfull
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
return
# Must not delete the temporary file on Windows # Must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False) self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
file_path = self._temp_plugin_file.name file_path = self._temp_plugin_file.name
@ -726,10 +686,10 @@ class Toolbox(QObject, Extension):
self._onDownloadComplete(file_path) self._onDownloadComplete(file_path)
def _onDownloadComplete(self, file_path: str) -> None: def _onDownloadComplete(self, file_path: str) -> None:
Logger.log("i", "Marketplace: Download complete.") Logger.log("i", "Download complete.")
package_info = self._package_manager.getPackageInfo(file_path) package_info = self._package_manager.getPackageInfo(file_path)
if not package_info: if not package_info:
Logger.log("w", "Marketplace: Package file [%s] was not a valid CuraPackage.", file_path) Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
return return
license_content = self._package_manager.getPackageLicense(file_path) license_content = self._package_manager.getPackageLicense(file_path)
@ -738,7 +698,6 @@ class Toolbox(QObject, Extension):
return return
self.install(file_path) self.install(file_path)
return
# Getter & Setters for Properties: # Getter & Setters for Properties:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@ -761,6 +720,7 @@ class Toolbox(QObject, Extension):
return self._is_downloading return self._is_downloading
def setActivePackage(self, package: Dict[str, Any]) -> None: def setActivePackage(self, package: Dict[str, Any]) -> None:
if self._active_package != package:
self._active_package = package self._active_package = package
self.activePackageChanged.emit() self.activePackageChanged.emit()
@ -770,6 +730,7 @@ class Toolbox(QObject, Extension):
return self._active_package return self._active_package
def setViewCategory(self, category: str = "plugin") -> None: def setViewCategory(self, category: str = "plugin") -> None:
if self._view_category != category:
self._view_category = category self._view_category = category
self.viewChanged.emit() self.viewChanged.emit()
@ -778,6 +739,7 @@ class Toolbox(QObject, Extension):
return self._view_category return self._view_category
def setViewPage(self, page: str = "overview") -> None: def setViewPage(self, page: str = "overview") -> None:
if self._view_page != page:
self._view_page = page self._view_page = page
self.viewChanged.emit() self.viewChanged.emit()
@ -787,48 +749,48 @@ class Toolbox(QObject, Extension):
# Exposed Models: # Exposed Models:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def authorsModel(self) -> AuthorsModel: def authorsModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["authors"]) return cast(AuthorsModel, self._models["authors"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def packagesModel(self) -> PackagesModel: def packagesModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["packages"]) return cast(PackagesModel, self._models["packages"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def pluginsShowcaseModel(self) -> PackagesModel: def pluginsShowcaseModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_showcase"]) return self._plugins_showcase_model
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def pluginsAvailableModel(self) -> PackagesModel: def pluginsAvailableModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_available"]) return self._plugins_available_model
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def pluginsInstalledModel(self) -> PackagesModel: def pluginsInstalledModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_installed"]) return self._plugins_installed_model
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def materialsShowcaseModel(self) -> AuthorsModel: def materialsShowcaseModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["materials_showcase"]) return self._materials_showcase_model
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def materialsAvailableModel(self) -> AuthorsModel: def materialsAvailableModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["materials_available"]) return self._materials_available_model
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, constant=True)
def materialsInstalledModel(self) -> PackagesModel: def materialsInstalledModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_installed"]) return self._materials_installed_model
@pyqtProperty(QObject, notify=metadataChanged) @pyqtProperty(QObject, constant=True)
def materialsGenericModel(self) -> PackagesModel: def materialsGenericModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_generic"]) return self._materials_generic_model
# Filter Models: # Filter Models:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@pyqtSlot(str, str, str) @pyqtSlot(str, str, str)
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None: def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
if not self._models[model_type]: if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type) Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return return
self._models[model_type].setFilter({filter_type: parameter}) self._models[model_type].setFilter({filter_type: parameter})
self.filterChanged.emit() self.filterChanged.emit()
@ -836,7 +798,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, "QVariantMap") @pyqtSlot(str, "QVariantMap")
def setFilters(self, model_type: str, filter_dict: dict) -> None: def setFilters(self, model_type: str, filter_dict: dict) -> None:
if not self._models[model_type]: if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type) Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return return
self._models[model_type].setFilter(filter_dict) self._models[model_type].setFilter(filter_dict)
self.filterChanged.emit() self.filterChanged.emit()
@ -844,21 +806,21 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str) @pyqtSlot(str)
def removeFilters(self, model_type: str) -> None: def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]: if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't remove filters on %s model because it doesn't exist.", model_type) Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
return return
self._models[model_type].setFilter({}) self._models[model_type].setFilter({})
self.filterChanged.emit() self.filterChanged.emit()
# HACK(S): # HACK(S):
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
def buildMaterialsModels(self) -> None: def reBuildMaterialsModels(self) -> None:
self._metadata["materials_showcase"] = [] materials_showcase_metadata = []
self._metadata["materials_available"] = [] materials_available_metadata = []
self._metadata["materials_generic"] = [] materials_generic_metadata = []
processed_authors = [] # type: List[str] processed_authors = [] # type: List[str]
for item in self._metadata["packages"]: for item in self._server_response_data["packages"]:
if item["package_type"] == "material": if item["package_type"] == "material":
author = item["author"] author = item["author"]
@ -867,30 +829,29 @@ class Toolbox(QObject, Extension):
# Generic materials to be in the same section # Generic materials to be in the same section
if "generic" in item["tags"]: if "generic" in item["tags"]:
self._metadata["materials_generic"].append(item) materials_generic_metadata.append(item)
else: else:
if "showcase" in item["tags"]: if "showcase" in item["tags"]:
self._metadata["materials_showcase"].append(author) materials_showcase_metadata.append(author)
else: else:
self._metadata["materials_available"].append(author) materials_available_metadata.append(author)
processed_authors.append(author["author_id"]) processed_authors.append(author["author_id"])
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"]) self._materials_showcase_model.setMetadata(materials_showcase_metadata)
self._models["materials_available"].setMetadata(self._metadata["materials_available"]) self._materials_available_model.setMetadata(materials_available_metadata)
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"]) self._materials_generic_model.setMetadata(materials_generic_metadata)
def buildPluginsModels(self) -> None: def reBuildPluginsModels(self) -> None:
self._metadata["plugins_showcase"] = [] plugins_showcase_metadata = []
self._metadata["plugins_available"] = [] plugins_available_metadata = []
for item in self._metadata["packages"]: for item in self._server_response_data["packages"]:
if item["package_type"] == "plugin": if item["package_type"] == "plugin":
if "showcase" in item["tags"]: if "showcase" in item["tags"]:
self._metadata["plugins_showcase"].append(item) plugins_showcase_metadata.append(item)
else: else:
self._metadata["plugins_available"].append(item) plugins_available_metadata.append(item)
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"]) self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
self._models["plugins_available"].setMetadata(self._metadata["plugins_available"]) self._plugins_available_model.setMetadata(plugins_available_metadata)