mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-06-26 01:15:28 -06:00
Delete old 'Toolbox' in favour of new Marketplace.
part of CURA-8588
This commit is contained in:
parent
cf7772a40a
commit
b794ad6ed2
55 changed files with 8 additions and 4822 deletions
|
@ -493,7 +493,7 @@ class CuraApplication(QtApplication):
|
|||
"CuraEngineBackend", #Cura is useless without this one since you can't slice.
|
||||
"FileLogger", #You want to be able to read the log if something goes wrong.
|
||||
"XmlMaterialProfile", #Cura crashes without this one.
|
||||
"Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
|
||||
"Marketplace", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
|
||||
"PrepareStage", #Cura is useless without this one since you can't load models.
|
||||
"PreviewStage", #This shows the list of the plugin views that are installed in Cura.
|
||||
"MonitorStage", #Major part of Cura's functionality.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from .src import Toolbox
|
||||
from .src.CloudSync.SyncOrchestrator import SyncOrchestrator
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
|
||||
def register(app):
|
||||
return {
|
||||
"extension": [Toolbox.Toolbox(app), SyncOrchestrator(app)]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "Toolbox",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"api": 7,
|
||||
"description": "Find, manage and install new Cura packages."
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M19,3H5C3.3,3,2,4.3,2,6v3c0,1.5,0.8,2.7,2,3.4V22h16v-9.6c1.2-0.7,2-2,2-3.4V6C22,4.3,20.7,3,19,3z
|
||||
M10,5h4v4c0,1.1-0.9,2-2,2s-2-0.9-2-2V5z M4,9V5h4v4c0,1.1-0.9,2-2,2S4,10.1,4,9z M18,20h-4v-5h-4v5H6v-7c1.2,0,2.3-0.5,3-1.4
|
||||
c0.7,0.8,1.8,1.4,3,1.4s2.3-0.5,3-1.4c0.7,0.8,1.8,1.4,3,1.4V20z M20,9c0,1.1-0.9,2-2,2s-2-0.9-2-2V5h4V9z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 458 B |
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path d="M0,512h512V0L0,512z M440.4,318.3L331.2,431.6c-1.4,1.4-2.7,2-4.8,2c-2,0-3.4-0.7-4.8-2l-53.3-57.3l-1.4-2
|
||||
c-1.4-1.4-2-3.4-2-4.8c0-1.4,0.7-3.4,2-4.8l9.6-9.6c2.7-2.7,6.8-2.7,9.6,0l0.7,0.7l37.6,40.2c1.4,1.4,3.4,1.4,4.8,0l91.4-94.9h0.7
|
||||
c2.7-2.7,6.8-2.7,9.6,0l9.5,9.6C443.1,311.5,443.1,315.6,440.4,318.3z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 667 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path d="M24,44,7,33.4V14.6L24,4,41,14.6V33.4ZM9,32.3l15,9.3,15-9.3V15.7L24,6.4,9,15.7Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 184 B |
|
@ -1,112 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
// Main window for the Toolbox
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "./pages"
|
||||
import "./dialogs"
|
||||
import "./components"
|
||||
|
||||
Window
|
||||
{
|
||||
id: base
|
||||
property var selection: null
|
||||
title: catalog.i18nc("@title", "Marketplace")
|
||||
modality: Qt.ApplicationModal
|
||||
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
||||
|
||||
width: UM.Theme.getSize("large_popup_dialog").width
|
||||
height: UM.Theme.getSize("large_popup_dialog").height
|
||||
minimumWidth: width
|
||||
maximumWidth: minimumWidth
|
||||
minimumHeight: height
|
||||
maximumHeight: minimumHeight
|
||||
color: UM.Theme.getColor("main_background")
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
Item
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
WelcomePage
|
||||
{
|
||||
visible: toolbox.viewPage === "welcome"
|
||||
}
|
||||
|
||||
ToolboxHeader
|
||||
{
|
||||
id: header
|
||||
visible: toolbox.viewPage !== "welcome"
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: mainView
|
||||
width: parent.width
|
||||
z: parent.z - 1
|
||||
anchors
|
||||
{
|
||||
top: header.bottom
|
||||
bottom: footer.top
|
||||
}
|
||||
// TODO: This could be improved using viewFilter instead of viewCategory
|
||||
ToolboxLoadingPage
|
||||
{
|
||||
id: viewLoading
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "loading"
|
||||
}
|
||||
ToolboxErrorPage
|
||||
{
|
||||
id: viewErrored
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "errored"
|
||||
}
|
||||
ToolboxDownloadsPage
|
||||
{
|
||||
id: viewDownloads
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "overview"
|
||||
}
|
||||
ToolboxDetailPage
|
||||
{
|
||||
id: viewDetail
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "detail"
|
||||
}
|
||||
ToolboxAuthorPage
|
||||
{
|
||||
id: viewAuthor
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "author"
|
||||
}
|
||||
ToolboxInstalledPage
|
||||
{
|
||||
id: installedPluginList
|
||||
visible: toolbox.viewCategory === "installed"
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxFooter
|
||||
{
|
||||
id: footer
|
||||
visible: toolbox.restartRequired
|
||||
height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
|
||||
}
|
||||
|
||||
Connections
|
||||
{
|
||||
target: toolbox
|
||||
function onShowLicenseDialog() { licenseDialog.show() }
|
||||
function onCloseLicenseDialog() { licenseDialog.close() }
|
||||
}
|
||||
|
||||
ToolboxLicenseDialog
|
||||
{
|
||||
id: licenseDialog
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
ButtonStyle
|
||||
{
|
||||
background: Rectangle
|
||||
{
|
||||
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
|
||||
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
|
||||
color: "transparent"
|
||||
border
|
||||
{
|
||||
width: UM.Theme.getSize("default_lining").width
|
||||
color: UM.Theme.getColor("lining")
|
||||
}
|
||||
}
|
||||
label: Label
|
||||
{
|
||||
text: control.text
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: sidebar
|
||||
height: parent.height
|
||||
width: UM.Theme.getSize("toolbox_back_column").width
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: UM.Theme.getSize("wide_margin").height
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: button
|
||||
text: catalog.i18nc("@action:button", "Back")
|
||||
enabled: !toolbox.isDownloading
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: backArrow
|
||||
anchors
|
||||
{
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
width: UM.Theme.getSize("standard_arrow").width
|
||||
height: UM.Theme.getSize("standard_arrow").height
|
||||
sourceSize
|
||||
{
|
||||
width: width
|
||||
height: height
|
||||
}
|
||||
color: button.enabled ? (button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
|
||||
source: UM.Theme.getIcon("ChevronSingleLeft")
|
||||
}
|
||||
width: UM.Theme.getSize("toolbox_back_button").width
|
||||
height: UM.Theme.getSize("toolbox_back_button").height
|
||||
onClicked:
|
||||
{
|
||||
toolbox.viewPage = "overview"
|
||||
if (toolbox.viewCategory == "material")
|
||||
{
|
||||
toolbox.filterModelByProp("authors", "package_types", "material")
|
||||
}
|
||||
else if (toolbox.viewCategory == "plugin")
|
||||
{
|
||||
toolbox.filterModelByProp("packages", "type", "plugin")
|
||||
}
|
||||
|
||||
}
|
||||
style: ButtonStyle
|
||||
{
|
||||
background: Rectangle
|
||||
{
|
||||
color: "transparent"
|
||||
}
|
||||
label: Label
|
||||
{
|
||||
id: labelStyle
|
||||
text: control.text
|
||||
color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
width: control.width
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.5 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
|
||||
property var packageData
|
||||
property var technicalDataSheetUrl: packageData.links.technicalDataSheet
|
||||
property var safetyDataSheetUrl: packageData.links.safetyDataSheet
|
||||
property var printingGuidelinesUrl: packageData.links.printingGuidelines
|
||||
property var materialWebsiteUrl: packageData.links.website
|
||||
|
||||
height: childrenRect.height
|
||||
onVisibleChanged: packageData.type === "material" && (compatibilityItem.visible || dataSheetLinks.visible)
|
||||
|
||||
Column
|
||||
{
|
||||
id: compatibilityItem
|
||||
visible: packageData.has_configs
|
||||
width: parent.width
|
||||
// This is a bit of a hack, but the whole QML is pretty messy right now. This needs a big overhaul.
|
||||
height: visible ? heading.height + table.height: 0
|
||||
|
||||
Label
|
||||
{
|
||||
id: heading
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@label", "Compatibility")
|
||||
wrapMode: Text.WordWrap
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
TableView
|
||||
{
|
||||
id: table
|
||||
width: parent.width
|
||||
frameVisible: false
|
||||
|
||||
// Workaround for scroll issues (QTBUG-49652)
|
||||
flickableItem.interactive: false
|
||||
Component.onCompleted:
|
||||
{
|
||||
for (var i = 0; i < flickableItem.children.length; ++i)
|
||||
{
|
||||
flickableItem.children[i].enabled = false
|
||||
}
|
||||
}
|
||||
selectionMode: 0
|
||||
model: packageData.supported_configs
|
||||
headerDelegate: Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("main_background")
|
||||
height: UM.Theme.getSize("toolbox_chart_row").height
|
||||
Label
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
text: styleData.value || ""
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
width: parent.width
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
rowDelegate: Item
|
||||
{
|
||||
height: UM.Theme.getSize("toolbox_chart_row").height
|
||||
Label
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
text: styleData.value || ""
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
itemDelegate: Item
|
||||
{
|
||||
height: UM.Theme.getSize("toolbox_chart_row").height
|
||||
Label
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
text: styleData.value || ""
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: columnTextDelegate
|
||||
Label
|
||||
{
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: styleData.value || ""
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn
|
||||
{
|
||||
role: "machine"
|
||||
title: catalog.i18nc("@label:table_header", "Machine")
|
||||
width: Math.floor(table.width * 0.25)
|
||||
delegate: columnTextDelegate
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "print_core"
|
||||
title: "Print Core" //This term should not be translated.
|
||||
width: Math.floor(table.width * 0.2)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "build_plate"
|
||||
title: catalog.i18nc("@label:table_header", "Build Plate")
|
||||
width: Math.floor(table.width * 0.225)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "support_material"
|
||||
title: catalog.i18nc("@label:table_header", "Support")
|
||||
width: Math.floor(table.width * 0.225)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "quality"
|
||||
title: catalog.i18nc("@label:table_header", "Quality")
|
||||
width: Math.floor(table.width * 0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: dataSheetLinks
|
||||
anchors.top: compatibilityItem.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
|
||||
visible: base.technicalDataSheetUrl !== undefined ||
|
||||
base.safetyDataSheetUrl !== undefined ||
|
||||
base.printingGuidelinesUrl !== undefined ||
|
||||
base.materialWebsiteUrl !== undefined
|
||||
|
||||
text:
|
||||
{
|
||||
var result = ""
|
||||
if (base.technicalDataSheetUrl !== undefined)
|
||||
{
|
||||
var tds_name = catalog.i18nc("@action:label", "Technical Data Sheet")
|
||||
result += "<a href='%1'>%2</a>".arg(base.technicalDataSheetUrl).arg(tds_name)
|
||||
}
|
||||
if (base.safetyDataSheetUrl !== undefined)
|
||||
{
|
||||
if (result.length > 0)
|
||||
{
|
||||
result += "<br/>"
|
||||
}
|
||||
var sds_name = catalog.i18nc("@action:label", "Safety Data Sheet")
|
||||
result += "<a href='%1'>%2</a>".arg(base.safetyDataSheetUrl).arg(sds_name)
|
||||
}
|
||||
if (base.printingGuidelinesUrl !== undefined)
|
||||
{
|
||||
if (result.length > 0)
|
||||
{
|
||||
result += "<br/>"
|
||||
}
|
||||
var pg_name = catalog.i18nc("@action:label", "Printing Guidelines")
|
||||
result += "<a href='%1'>%2</a>".arg(base.printingGuidelinesUrl).arg(pg_name)
|
||||
}
|
||||
if (base.materialWebsiteUrl !== undefined)
|
||||
{
|
||||
if (result.length > 0)
|
||||
{
|
||||
result += "<br/>"
|
||||
}
|
||||
var pg_name = catalog.i18nc("@action:label", "Website")
|
||||
result += "<a href='%1'>%2</a>".arg(base.materialWebsiteUrl).arg(pg_name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"])
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: detailList
|
||||
ScrollView
|
||||
{
|
||||
clip: true
|
||||
anchors.fill: detailList
|
||||
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
topMargin: UM.Theme.getSize("wide_margin").height
|
||||
bottomMargin: UM.Theme.getSize("wide_margin").height
|
||||
top: parent.top
|
||||
}
|
||||
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: toolbox.packagesModel
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: tile
|
||||
width: detailList.width - UM.Theme.getSize("wide_margin").width
|
||||
height: normalData.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||
Column
|
||||
{
|
||||
id: normalData
|
||||
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: controls.left
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_property_label").height
|
||||
text: model.name
|
||||
wrapMode: Text.WordWrap
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
text: model.description
|
||||
maximumLineCount: 25
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
ToolboxCompatibilityChart
|
||||
{
|
||||
width: parent.width
|
||||
packageData: model
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxDetailTileActions
|
||||
{
|
||||
id: controls
|
||||
anchors.right: tile.right
|
||||
anchors.top: tile.top
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
packageData: model
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("lining")
|
||||
width: tile.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.top: normalData.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height + UM.Theme.getSize("wide_margin").height //Normal margin for spacing after chart, wide margin between items.
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.5 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Column
|
||||
{
|
||||
property bool installed: toolbox.isInstalled(model.id)
|
||||
property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1
|
||||
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||
property var packageData
|
||||
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
spacing: UM.Theme.getSize("narrow_margin").height
|
||||
|
||||
Item
|
||||
{
|
||||
width: installButton.width
|
||||
height: installButton.height
|
||||
ToolboxProgressButton
|
||||
{
|
||||
id: installButton
|
||||
active: toolbox.isDownloading && toolbox.activePackage == model
|
||||
onReadyAction:
|
||||
{
|
||||
toolbox.activePackage = model
|
||||
toolbox.startDownload(model.download_url)
|
||||
}
|
||||
onActiveAction: toolbox.cancelDownload()
|
||||
|
||||
// Don't allow installing while another download is running
|
||||
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
visible: !updateButton.visible && !installed // Don't show when the update button is visible
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: installedButton
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
property var whereToBuyUrl:
|
||||
{
|
||||
var pg_name = "whereToBuy"
|
||||
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
|
||||
}
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Buy material spools</a>")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
visible: whereToBuyUrl != undefined
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: UM.UrlUtil.openUrl(parent.whereToBuyUrl, ["https", "http"])
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxProgressButton
|
||||
{
|
||||
id: updateButton
|
||||
active: toolbox.isDownloading && toolbox.activePackage == model
|
||||
readyLabel: catalog.i18nc("@action:button", "Update")
|
||||
activeLabel: catalog.i18nc("@action:button", "Updating")
|
||||
completeLabel: catalog.i18nc("@action:button", "Updated")
|
||||
|
||||
onReadyAction:
|
||||
{
|
||||
toolbox.activePackage = model
|
||||
toolbox.update(model.id)
|
||||
}
|
||||
onActiveAction: toolbox.cancelDownload()
|
||||
// Don't allow installing while another download is running
|
||||
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
visible: canUpdate
|
||||
}
|
||||
|
||||
Connections
|
||||
{
|
||||
target: toolbox
|
||||
function onInstallChanged() { installed = toolbox.isInstalled(model.id) }
|
||||
function onFilterChanged()
|
||||
{
|
||||
installed = toolbox.isInstalled(model.id)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Column
|
||||
{
|
||||
property var heading: ""
|
||||
property var model
|
||||
id: gridArea
|
||||
height: childrenRect.height + 2 * padding
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
padding: UM.Theme.getSize("wide_margin").height
|
||||
Label
|
||||
{
|
||||
id: heading
|
||||
text: gridArea.heading
|
||||
width: parent.width
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("large")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Grid
|
||||
{
|
||||
id: grid
|
||||
width: parent.width - 2 * parent.padding
|
||||
columns: 2
|
||||
columnSpacing: UM.Theme.getSize("default_margin").height
|
||||
rowSpacing: UM.Theme.getSize("default_margin").width
|
||||
Repeater
|
||||
{
|
||||
model: gridArea.model
|
||||
delegate: Loader
|
||||
{
|
||||
asynchronous: true
|
||||
width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_small").height
|
||||
source: "ToolboxDownloadsGridTile.qml"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: toolboxDownloadsGridTile
|
||||
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
height: childrenRect.height
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: thumbnail.border.color = UM.Theme.getColor("primary")
|
||||
onExited: thumbnail.border.color = UM.Theme.getColor("lining")
|
||||
onClicked:
|
||||
{
|
||||
base.selection = model
|
||||
switch(toolbox.viewCategory)
|
||||
{
|
||||
case "material":
|
||||
|
||||
// If model has a type, it must be a package
|
||||
if (model.type !== undefined)
|
||||
{
|
||||
toolbox.viewPage = "detail"
|
||||
toolbox.filterModelByProp("packages", "id", model.id)
|
||||
}
|
||||
else
|
||||
{
|
||||
toolbox.viewPage = "author"
|
||||
toolbox.setFilters("packages", {
|
||||
"author_id": model.id,
|
||||
"type": "material"
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
toolbox.viewPage = "detail"
|
||||
toolbox.filterModelByProp("packages", "id", model.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: thumbnail
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_small").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_small").height
|
||||
color: UM.Theme.getColor("main_background")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
|
||||
Image
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.icon_url || "../../images/placeholder.svg"
|
||||
mipmap: true
|
||||
}
|
||||
UM.RecolorImage
|
||||
{
|
||||
width: (parent.width * 0.4) | 0
|
||||
height: (parent.height * 0.4) | 0
|
||||
anchors
|
||||
{
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
sourceSize.height: height
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../../images/installed_check.svg"
|
||||
}
|
||||
}
|
||||
Item
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: thumbnail.right
|
||||
leftMargin: Math.floor(UM.Theme.getSize("narrow_margin").width)
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: name
|
||||
text: model.name
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: info
|
||||
text: model.description
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default")
|
||||
anchors.top: name.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLineCount: 2
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("toolbox_premium_packages_background")
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
Column
|
||||
{
|
||||
height: childrenRect.height + 2 * padding
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
padding: UM.Theme.getSize("wide_margin").height
|
||||
Item
|
||||
{
|
||||
width: parent.width - parent.padding * 2
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
id: heading
|
||||
text: catalog.i18nc("@label", "Premium")
|
||||
width: contentWidth
|
||||
height: contentHeight
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("large")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
UM.TooltipArea
|
||||
{
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@info:tooltip", "Go to Web Marketplace")
|
||||
Label
|
||||
{
|
||||
text: "<a href='%2'>".arg(toolbox.getWebMarketplaceUrl("materials") + "?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search") + catalog.i18nc("@label", "Search materials") + "</a>"
|
||||
width: contentWidth
|
||||
height: contentHeight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
visible: toolbox.viewCategory === "material"
|
||||
}
|
||||
}
|
||||
}
|
||||
Grid
|
||||
{
|
||||
height: childrenRect.height
|
||||
spacing: UM.Theme.getSize("wide_margin").width
|
||||
columns: 3
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater
|
||||
{
|
||||
model:
|
||||
{
|
||||
if (toolbox.viewCategory == "plugin")
|
||||
{
|
||||
return toolbox.pluginsShowcaseModel
|
||||
}
|
||||
if (toolbox.viewCategory == "material")
|
||||
{
|
||||
return toolbox.materialsShowcaseModel
|
||||
}
|
||||
}
|
||||
delegate: Loader
|
||||
{
|
||||
asynchronous: true
|
||||
source: "ToolboxDownloadsShowcaseTile.qml"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
id: tileBase
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
|
||||
height: thumbnail.height + packageName.height + UM.Theme.getSize("default_margin").width
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
color: UM.Theme.getColor("main_background")
|
||||
Image
|
||||
{
|
||||
id: thumbnail
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.icon_url || "../../images/placeholder.svg"
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: packageName
|
||||
text: model.name
|
||||
anchors
|
||||
{
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: thumbnail.bottom
|
||||
}
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
renderType: Text.NativeRendering
|
||||
height: UM.Theme.getSize("toolbox_heading_label").height
|
||||
width: parent.width - UM.Theme.getSize("default_margin").width
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
UM.RecolorImage
|
||||
{
|
||||
width: (parent.width * 0.20) | 0
|
||||
height: width
|
||||
anchors
|
||||
{
|
||||
bottom: bottomBorder.top
|
||||
right: parent.right
|
||||
}
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../../images/installed_check.svg"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: bottomBorder
|
||||
color: UM.Theme.getColor("primary")
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_header_highlight").height
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: tileBase.border.color = UM.Theme.getColor("primary")
|
||||
onExited: tileBase.border.color = UM.Theme.getColor("lining")
|
||||
onClicked:
|
||||
{
|
||||
base.selection = model
|
||||
switch(toolbox.viewCategory)
|
||||
{
|
||||
case "material":
|
||||
|
||||
// If model has a type, it must be a package
|
||||
if (model.type !== undefined)
|
||||
{
|
||||
toolbox.viewPage = "detail"
|
||||
toolbox.filterModelByProp("packages", "id", model.id)
|
||||
}
|
||||
else
|
||||
{
|
||||
toolbox.viewPage = "author"
|
||||
toolbox.setFilters("packages", {
|
||||
"author_id": model.id,
|
||||
"type": "material"
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
toolbox.viewPage = "detail"
|
||||
toolbox.filterModelByProp("packages", "id", model.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: footer
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
|
||||
color: UM.Theme.getColor("text")
|
||||
height: UM.Theme.getSize("toolbox_footer_button").height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
anchors
|
||||
{
|
||||
top: restartButton.top
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
right: restartButton.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: restartButton
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
height: UM.Theme.getSize("toolbox_footer_button").height
|
||||
text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName)
|
||||
onClicked:
|
||||
{
|
||||
base.hide()
|
||||
toolbox.restart()
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxShadow
|
||||
{
|
||||
visible: footer.visible
|
||||
anchors.bottom: footer.top
|
||||
reversed: true
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.4 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: header
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_header").height
|
||||
Row
|
||||
{
|
||||
id: bar
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
height: childrenRect.height
|
||||
width: childrenRect.width
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: pluginsTabButton
|
||||
text: catalog.i18nc("@title:tab", "Plugins")
|
||||
active: toolbox.viewCategory == "plugin" && enabled
|
||||
enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
|
||||
onClicked:
|
||||
{
|
||||
toolbox.filterModelByProp("packages", "type", "plugin")
|
||||
toolbox.viewCategory = "plugin"
|
||||
toolbox.viewPage = "overview"
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: materialsTabButton
|
||||
text: catalog.i18nc("@title:tab", "Materials")
|
||||
active: toolbox.viewCategory == "material" && enabled
|
||||
enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
|
||||
onClicked:
|
||||
{
|
||||
toolbox.filterModelByProp("authors", "package_types", "material")
|
||||
toolbox.viewCategory = "material"
|
||||
toolbox.viewPage = "overview"
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: installedTabButton
|
||||
text: catalog.i18nc("@title:tab", "Installed")
|
||||
active: toolbox.viewCategory == "installed"
|
||||
enabled: !toolbox.isDownloading
|
||||
onClicked: toolbox.viewCategory = "installed"
|
||||
width: UM.Theme.getSize("toolbox_header_tab").width + marketplaceNotificationIcon.width - UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Cura.NotificationIcon
|
||||
{
|
||||
id: marketplaceNotificationIcon
|
||||
visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0
|
||||
anchors.right: bar.right
|
||||
labelText:
|
||||
{
|
||||
const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length
|
||||
return itemCount > 9 ? "9+" : itemCount
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UM.TooltipArea
|
||||
{
|
||||
id: webMarketplaceButtonTooltipArea
|
||||
width: childrenRect.width
|
||||
height: parent.height
|
||||
text: catalog.i18nc("@info:tooltip", "Go to Web Marketplace")
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: Qt.openUrlExternally(toolbox.getWebMarketplaceUrl("plugins") + "?utm_source=cura&utm_medium=software&utm_campaign=marketplace-button")
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: cloudMarketplaceButton
|
||||
source: "../../images/Shop.svg"
|
||||
color: UM.Theme.getColor(webMarketplaceButtonTooltipArea.containsMouse ? "primary" : "text")
|
||||
height: parent.height / 2
|
||||
width: height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxShadow
|
||||
{
|
||||
anchors.top: bar.bottom
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
height: UM.Theme.getSize("toolbox_installed_tile").height
|
||||
width: parent.width
|
||||
property bool isEnabled: true
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("lining")
|
||||
width: parent.width
|
||||
height: Math.floor(UM.Theme.getSize("default_lining").height)
|
||||
anchors.bottom: parent.top
|
||||
visible: index != 0
|
||||
}
|
||||
Row
|
||||
{
|
||||
id: tileRow
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
topPadding: UM.Theme.getSize("default_margin").height
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: disableButton
|
||||
anchors.verticalCenter: pluginInfo.verticalCenter
|
||||
checked: isEnabled
|
||||
visible: model.type == "plugin"
|
||||
width: visible ? UM.Theme.getSize("checkbox").width : 0
|
||||
enabled: !toolbox.isDownloading
|
||||
style: UM.Theme.styles.checkbox
|
||||
onClicked: toolbox.isEnabled(model.id) ? toolbox.disable(model.id) : toolbox.enable(model.id)
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: pluginInfo
|
||||
topPadding: UM.Theme.getSize("narrow_margin").height
|
||||
property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text")
|
||||
width: Math.floor(tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0)))
|
||||
Label
|
||||
{
|
||||
text: model.name
|
||||
width: parent.width
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
color: pluginInfo.color
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: model.description
|
||||
font: UM.Theme.getFont("default")
|
||||
maximumLineCount: 3
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
color: pluginInfo.color
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: authorInfo
|
||||
width: Math.floor(UM.Theme.getSize("toolbox_action_button").width * 1.25)
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (model.author_email)
|
||||
{
|
||||
return "<a href=\"mailto:" + model.author_email + "?Subject=Cura: " + model.name + "\">" + model.author_name + "</a>"
|
||||
}
|
||||
else
|
||||
{
|
||||
return model.author_name
|
||||
}
|
||||
}
|
||||
font: UM.Theme.getFont("medium")
|
||||
width: parent.width
|
||||
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
|
||||
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")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: model.version
|
||||
font: UM.Theme.getFont("default")
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_property_label").height
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
ToolboxInstalledTileActions
|
||||
{
|
||||
id: pluginActions
|
||||
}
|
||||
Connections
|
||||
{
|
||||
target: toolbox
|
||||
function onToolboxEnabledChanged() { isEnabled = toolbox.isEnabled(model.id) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Column
|
||||
{
|
||||
property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1
|
||||
property bool canDowngrade: false
|
||||
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
spacing: UM.Theme.getSize("narrow_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
visible: !model.is_installed
|
||||
text: catalog.i18nc("@label", "Will install upon restarting")
|
||||
color: UM.Theme.getColor("lining")
|
||||
font: UM.Theme.getFont("default")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
ToolboxProgressButton
|
||||
{
|
||||
id: updateButton
|
||||
active: toolbox.isDownloading && toolbox.activePackage == model
|
||||
readyLabel: catalog.i18nc("@action:button", "Update")
|
||||
activeLabel: catalog.i18nc("@action:button", "Updating")
|
||||
completeLabel: catalog.i18nc("@action:button", "Updated")
|
||||
onReadyAction:
|
||||
{
|
||||
toolbox.activePackage = model
|
||||
toolbox.update(model.id)
|
||||
}
|
||||
onActiveAction: toolbox.cancelDownload()
|
||||
|
||||
// Don't allow installing while another download is running
|
||||
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
visible: canUpdate
|
||||
}
|
||||
|
||||
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
|
||||
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
|
||||
visible: !model.is_bundled && model.is_installed
|
||||
enabled: !toolbox.isDownloading
|
||||
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
height: UM.Theme.getSize("toolbox_action_button").height
|
||||
|
||||
fixedWidthMode: true
|
||||
|
||||
onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
|
||||
Connections
|
||||
{
|
||||
target: toolbox
|
||||
function onMetadataChanged()
|
||||
{
|
||||
canDowngrade = toolbox.canDowngrade(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: button
|
||||
|
||||
property var active: false
|
||||
property var complete: false
|
||||
|
||||
property var readyLabel: catalog.i18nc("@action:button", "Install")
|
||||
property var activeLabel: catalog.i18nc("@action:button", "Cancel")
|
||||
property var completeLabel: catalog.i18nc("@action:button", "Installed")
|
||||
|
||||
signal readyAction() // Action when button is ready and clicked (likely install)
|
||||
signal activeAction() // Action when button is active and clicked (likely cancel)
|
||||
signal completeAction() // Action when button is complete and clicked (likely go to installed)
|
||||
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
height: UM.Theme.getSize("toolbox_action_button").height
|
||||
fixedWidthMode: true
|
||||
text:
|
||||
{
|
||||
if (complete)
|
||||
{
|
||||
return completeLabel
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
return activeLabel
|
||||
}
|
||||
else
|
||||
{
|
||||
return readyLabel
|
||||
}
|
||||
}
|
||||
onClicked:
|
||||
{
|
||||
if (complete)
|
||||
{
|
||||
completeAction()
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
activeAction()
|
||||
}
|
||||
else
|
||||
{
|
||||
readyAction()
|
||||
}
|
||||
}
|
||||
busy: active
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
|
||||
Rectangle
|
||||
{
|
||||
property bool reversed: false
|
||||
width: parent.width
|
||||
height: 8
|
||||
gradient: Gradient
|
||||
{
|
||||
GradientStop
|
||||
{
|
||||
position: reversed ? 1.0 : 0.0
|
||||
color: reversed ? Qt.rgba(0,0,0,0.05) : Qt.rgba(0,0,0,0.2)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: reversed ? 0.0 : 1.0
|
||||
color: Qt.rgba(0,0,0,0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Button
|
||||
{
|
||||
id: control
|
||||
property bool active: false
|
||||
|
||||
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
|
||||
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
|
||||
|
||||
background: Item
|
||||
{
|
||||
id: backgroundItem
|
||||
Rectangle
|
||||
{
|
||||
id: highlight
|
||||
|
||||
visible: control.active
|
||||
color: UM.Theme.getColor("primary")
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_header_highlight").height
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
id: label
|
||||
text: control.text
|
||||
color: UM.Theme.getColor("toolbox_header_button_text_inactive")
|
||||
font: UM.Theme.getFont("medium")
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
states:
|
||||
[
|
||||
State
|
||||
{
|
||||
name: "disabled"
|
||||
when: !control.enabled
|
||||
PropertyChanges
|
||||
{
|
||||
target: label
|
||||
font: UM.Theme.getFont("default_italic")
|
||||
}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "active"
|
||||
when: control.active
|
||||
PropertyChanges
|
||||
{
|
||||
target: label
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("action_button_text")
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
|
||||
UM.Dialog{
|
||||
visible: true
|
||||
title: catalog.i18nc("@title", "Changes from your account")
|
||||
width: UM.Theme.getSize("popup_dialog").width
|
||||
height: UM.Theme.getSize("popup_dialog").height
|
||||
minimumWidth: width
|
||||
maximumWidth: minimumWidth
|
||||
minimumHeight: height
|
||||
maximumHeight: minimumHeight
|
||||
margin: 0
|
||||
|
||||
property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next")
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
width: parent.width
|
||||
height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom
|
||||
clip: true
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
// Compatible packages
|
||||
Label
|
||||
{
|
||||
font: UM.Theme.getFont("default")
|
||||
text: catalog.i18nc("@label", "The following packages will be added:")
|
||||
visible: subscribedPackagesModel.hasCompatiblePackages
|
||||
color: UM.Theme.getColor("text")
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
model: subscribedPackagesModel
|
||||
Component
|
||||
{
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
property int lineHeight: 60
|
||||
visible: model.is_compatible
|
||||
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here
|
||||
Image
|
||||
{
|
||||
id: packageIcon
|
||||
source: model.icon_url || "../../images/placeholder.svg"
|
||||
height: lineHeight
|
||||
width: height
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: model.display_name
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
anchors.left: packageIcon.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: packageIcon.verticalCenter
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incompatible packages
|
||||
Label
|
||||
{
|
||||
font: UM.Theme.getFont("default")
|
||||
text: catalog.i18nc("@label", "The following packages can not be installed because of an incompatible Cura version:")
|
||||
visible: subscribedPackagesModel.hasIncompatiblePackages
|
||||
color: UM.Theme.getColor("text")
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
model: subscribedPackagesModel
|
||||
Component
|
||||
{
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
property int lineHeight: 60
|
||||
visible: !model.is_compatible && !model.is_dismissed
|
||||
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here
|
||||
Image
|
||||
{
|
||||
id: packageIcon
|
||||
source: model.icon_url || "../../images/placeholder.svg"
|
||||
height: lineHeight
|
||||
width: height
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: model.display_name
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
anchors.left: packageIcon.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: packageIcon.verticalCenter
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of ScrollView
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: nextButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
text: actionButtonText
|
||||
onClicked: accept()
|
||||
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
|
||||
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
// This dialog asks the user to confirm he/she wants to uninstall materials/pprofiles which are currently in use
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall
|
||||
width: 450 * screenScaleFactor
|
||||
height: 50 * screenScaleFactor + dialogText.height + buttonBar.height
|
||||
|
||||
maximumWidth: 450 * screenScaleFactor
|
||||
maximumHeight: 450 * screenScaleFactor
|
||||
minimumWidth: 450 * screenScaleFactor
|
||||
minimumHeight: 150 * screenScaleFactor
|
||||
|
||||
modality: Qt.WindowModal
|
||||
|
||||
Column
|
||||
{
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
anchors
|
||||
{
|
||||
fill: parent
|
||||
leftMargin: Math.round(20 * screenScaleFactor)
|
||||
rightMargin: Math.round(20 * screenScaleFactor)
|
||||
topMargin: Math.round(10 * screenScaleFactor)
|
||||
bottomMargin: Math.round(10 * screenScaleFactor)
|
||||
}
|
||||
spacing: Math.round(15 * screenScaleFactor)
|
||||
|
||||
Label
|
||||
{
|
||||
id: dialogText
|
||||
text:
|
||||
{
|
||||
var base_text = catalog.i18nc("@text:window", "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults.")
|
||||
var materials_text = catalog.i18nc("@text:window", "Materials")
|
||||
var qualities_text = catalog.i18nc("@text:window", "Profiles")
|
||||
var machines_with_materials = toolbox.uninstallUsedMaterials
|
||||
var machines_with_qualities = toolbox.uninstallUsedQualities
|
||||
if (machines_with_materials != "")
|
||||
{
|
||||
base_text += "\n\n" + materials_text +": \n" + machines_with_materials
|
||||
}
|
||||
if (machines_with_qualities != "")
|
||||
{
|
||||
base_text += "\n\n" + qualities_text + ": \n" + machines_with_qualities
|
||||
}
|
||||
return base_text
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
font: UM.Theme.getFont("default")
|
||||
wrapMode: Text.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
// Buttons
|
||||
Item {
|
||||
id: buttonBar
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
height: childrenRect.height
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
text: catalog.i18nc("@action:button", "Cancel")
|
||||
anchors.right: confirmButton.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
isDefault: true
|
||||
onClicked: toolbox.closeConfirmResetDialog()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: confirmButton
|
||||
text: catalog.i18nc("@action:button", "Confirm")
|
||||
anchors.right: parent.right
|
||||
onClicked: toolbox.resetMaterialsQualitiesAndUninstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: licenseDialog
|
||||
title: licenseModel.dialogTitle
|
||||
minimumWidth: UM.Theme.getSize("license_window_minimum").width
|
||||
minimumHeight: UM.Theme.getSize("license_window_minimum").height
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
backgroundColor: UM.Theme.getColor("main_background")
|
||||
margin: screenScaleFactor * 10
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
anchors.fill: parent
|
||||
spacing: UM.Theme.getSize("thick_margin").height
|
||||
|
||||
UM.I18nCatalog{id: catalog; name: "cura"}
|
||||
|
||||
Label
|
||||
{
|
||||
id: licenseHeader
|
||||
Layout.fillWidth: true
|
||||
text: catalog.i18nc("@label", "You need to accept the license to install the package")
|
||||
color: UM.Theme.getColor("text")
|
||||
wrapMode: Text.Wrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Row {
|
||||
id: packageRow
|
||||
|
||||
Layout.fillWidth: true
|
||||
height: childrenRect.height
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
leftPadding: UM.Theme.getSize("narrow_margin").width
|
||||
|
||||
Image
|
||||
{
|
||||
id: icon
|
||||
width: 30 * screenScaleFactor
|
||||
height: width
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: licenseModel.iconUrl || "../../images/placeholder.svg"
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: packageName
|
||||
text: licenseModel.packageName
|
||||
color: UM.Theme.getColor("text")
|
||||
font.bold: true
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
height: contentHeight
|
||||
wrapMode: Text.Wrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Cura.ScrollableTextArea
|
||||
{
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
textArea.text: licenseModel.licenseText
|
||||
textArea.readOnly: true
|
||||
}
|
||||
|
||||
}
|
||||
rightButtons:
|
||||
[
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
|
||||
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
|
||||
|
||||
text: licenseModel.acceptButtonText
|
||||
onClicked: { handler.onLicenseAccepted() }
|
||||
}
|
||||
]
|
||||
|
||||
leftButtons:
|
||||
[
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: declineButton
|
||||
text: licenseModel.declineButtonText
|
||||
onClicked: { handler.onLicenseDeclined() }
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.5 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
Item
|
||||
{
|
||||
id: page
|
||||
property var details: base.selection || {}
|
||||
anchors.fill: parent
|
||||
ToolboxBackColumn
|
||||
{
|
||||
id: sidebar
|
||||
}
|
||||
Item
|
||||
{
|
||||
id: header
|
||||
anchors
|
||||
{
|
||||
left: sidebar.right
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
height: UM.Theme.getSize("toolbox_detail_header").height
|
||||
Image
|
||||
{
|
||||
id: thumbnail
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details && details.icon_url ? details.icon_url : "../../images/placeholder.svg"
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
topMargin: UM.Theme.getSize("wide_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: title
|
||||
anchors
|
||||
{
|
||||
top: thumbnail.top
|
||||
left: thumbnail.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
text: details && details.name ? details.name : ""
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_property_label").height
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: description
|
||||
text: details && details.description ? details.description : ""
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
anchors
|
||||
{
|
||||
top: title.bottom
|
||||
left: title.left
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: properties
|
||||
anchors
|
||||
{
|
||||
top: description.bottom
|
||||
left: description.left
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
width: childrenRect.width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Website") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Email") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: values
|
||||
anchors
|
||||
{
|
||||
top: description.bottom
|
||||
left: properties.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details && details.website)
|
||||
{
|
||||
return "<a href=\"" + details.website + "\">" + details.website + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: UM.UrlUtil.openUrl(link, ["https", "http"])
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details && details.email)
|
||||
{
|
||||
return "<a href=\"mailto:" + details.email + "\">" + details.email + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("lining")
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
ToolboxDetailList
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: header.bottom
|
||||
bottom: page.bottom
|
||||
left: header.left
|
||||
right: page.right
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.5 as UM
|
||||
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
Item
|
||||
{
|
||||
id: page
|
||||
property var details: base.selection || {}
|
||||
anchors.fill: parent
|
||||
ToolboxBackColumn
|
||||
{
|
||||
id: sidebar
|
||||
}
|
||||
Item
|
||||
{
|
||||
id: header
|
||||
anchors
|
||||
{
|
||||
left: sidebar.right
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
height: childrenRect.height + 3 * UM.Theme.getSize("default_margin").width
|
||||
Rectangle
|
||||
{
|
||||
id: thumbnail
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
topMargin: UM.Theme.getSize("wide_margin").height
|
||||
}
|
||||
color: UM.Theme.getColor("main_background")
|
||||
Image
|
||||
{
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details === null ? "" : (details.icon_url || "../../images/placeholder.svg")
|
||||
mipmap: true
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: title
|
||||
anchors
|
||||
{
|
||||
top: thumbnail.top
|
||||
left: thumbnail.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
text: details === null ? "" : (details.name || "")
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
width: contentWidth
|
||||
height: contentHeight
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: properties
|
||||
anchors
|
||||
{
|
||||
top: title.bottom
|
||||
left: title.left
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Version") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Last updated") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Brand") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Downloads") + ":"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: values
|
||||
anchors
|
||||
{
|
||||
top: title.bottom
|
||||
left: properties.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
var date = new Date(details.last_updated)
|
||||
return date.toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
else
|
||||
{
|
||||
return "<a href=\"" + details.website + "\">" + details.author_name + "</a>"
|
||||
}
|
||||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"])
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolboxDetailList
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: header.bottom
|
||||
bottom: page.bottom
|
||||
left: header.left
|
||||
right: page.right
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
ScrollView
|
||||
{
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: mainColumn.height
|
||||
|
||||
Column
|
||||
{
|
||||
id: mainColumn
|
||||
width: base.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
ToolboxDownloadsShowcase
|
||||
{
|
||||
id: showcase
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
ToolboxDownloadsGrid
|
||||
{
|
||||
id: allPlugins
|
||||
width: parent.width
|
||||
heading: toolbox.viewCategory === "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
|
||||
model: toolbox.viewCategory === "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
|
||||
}
|
||||
|
||||
ToolboxDownloadsGrid
|
||||
{
|
||||
id: genericMaterials
|
||||
visible: toolbox.viewCategory === "material"
|
||||
width: parent.width
|
||||
heading: catalog.i18nc("@label", "Generic Materials")
|
||||
model: toolbox.materialsGenericModel
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: page
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@info", "Could not connect to the Cura Package database. Please check your connection.")
|
||||
anchors
|
||||
{
|
||||
centerIn: parent
|
||||
}
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: page
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Column
|
||||
{
|
||||
width: page.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
padding: UM.Theme.getSize("wide_margin").width
|
||||
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Installed plugins")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: installedPlugins
|
||||
color: "transparent"
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
id: pluginList
|
||||
model: toolbox.pluginsInstalledModel
|
||||
delegate: ToolboxInstalledTile { }
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
visible: toolbox.pluginsInstalledModel.count < 1
|
||||
padding: UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@info", "No plugin has been installed.")
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("lining")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Installed materials")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: installedMaterials
|
||||
color: "transparent"
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
id: installedMaterialsList
|
||||
model: toolbox.materialsInstalledModel
|
||||
delegate: ToolboxInstalledTile { }
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
visible: toolbox.materialsInstalledModel.count < 1
|
||||
padding: UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@info", "No material has been installed.")
|
||||
color: UM.Theme.getColor("lining")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Bundled plugins")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: bundledPlugins
|
||||
color: "transparent"
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
id: bundledPluginsList
|
||||
model: toolbox.pluginsBundledModel
|
||||
delegate: ToolboxInstalledTile { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Bundled materials")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: bundledMaterials
|
||||
color: "transparent"
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
id: bundledMaterialsList
|
||||
model: toolbox.materialsBundledModel
|
||||
delegate: ToolboxInstalledTile {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.3 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: page
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@info", "Fetching packages...")
|
||||
color: UM.Theme.getColor("text")
|
||||
anchors
|
||||
{
|
||||
centerIn: parent
|
||||
}
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Column
|
||||
{
|
||||
id: welcomePage
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
Label
|
||||
{
|
||||
id: welcomeTextLabel
|
||||
text: catalog.i18nc("@description", "Please sign in to get verified plugins and materials for Ultimaker Cura Enterprise")
|
||||
width: Math.round(parent.width / 2)
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Label.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: loginButton
|
||||
width: UM.Theme.getSize("account_button").width
|
||||
height: UM.Theme.getSize("account_button").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: catalog.i18nc("@button", "Sign in")
|
||||
onClicked: Cura.API.account.login()
|
||||
fixedWidthMode: true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re
|
||||
from typing import Dict, List, Optional, Union, cast
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtProperty
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
class AuthorsModel(ListModel):
|
||||
"""Model that holds cura packages.
|
||||
|
||||
By setting the filter property the instances held by this model can be changed.
|
||||
"""
|
||||
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]]
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "id")
|
||||
self.addRoleName(Qt.UserRole + 2, "name")
|
||||
self.addRoleName(Qt.UserRole + 3, "email")
|
||||
self.addRoleName(Qt.UserRole + 4, "website")
|
||||
self.addRoleName(Qt.UserRole + 5, "package_count")
|
||||
self.addRoleName(Qt.UserRole + 6, "package_types")
|
||||
self.addRoleName(Qt.UserRole + 7, "icon_url")
|
||||
self.addRoleName(Qt.UserRole + 8, "description")
|
||||
|
||||
# List of filters for queries. The result is the union of the each list of results.
|
||||
self._filter = {} # type: Dict[str, str]
|
||||
|
||||
def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]):
|
||||
if self._metadata != data:
|
||||
self._metadata = data
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
items = [] # type: List[Dict[str, Union[str, List[str], int, None]]]
|
||||
if not self._metadata:
|
||||
self.setItems(items)
|
||||
return
|
||||
|
||||
for author in self._metadata:
|
||||
items.append({
|
||||
"id": author.get("author_id"),
|
||||
"name": author.get("display_name"),
|
||||
"email": author.get("email"),
|
||||
"website": author.get("website"),
|
||||
"package_count": author.get("package_count", 0),
|
||||
"package_types": author.get("package_types", []),
|
||||
"icon_url": author.get("icon_url"),
|
||||
"description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", ""))
|
||||
})
|
||||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key == "package_types":
|
||||
key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
|
||||
elif "*" in value:
|
||||
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
|
||||
else:
|
||||
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore
|
||||
items = filter(key_filter, items) # type: ignore
|
||||
|
||||
# Execute all filters.
|
||||
filtered_items = list(items)
|
||||
|
||||
filtered_items.sort(key = lambda k: cast(str, k["name"]))
|
||||
self.setItems(filtered_items)
|
||||
|
||||
def setFilter(self, filter_dict: Dict[str, str]) -> None:
|
||||
"""Set the filter of this model based on a string.
|
||||
|
||||
:param filter_dict: Dictionary to do the filtering by.
|
||||
"""
|
||||
if filter_dict != self._filter:
|
||||
self._filter = filter_dict
|
||||
self._update()
|
||||
|
||||
@pyqtProperty("QVariantMap", fset = setFilter, constant = True)
|
||||
def filter(self) -> Dict[str, str]:
|
||||
return self._filter
|
||||
|
||||
# Check to see if a container matches with a regular expression
|
||||
def _matchRegExp(self, metadata, property_name, value):
|
||||
if property_name not in metadata:
|
||||
return False
|
||||
value = re.escape(value) #Escape for regex patterns.
|
||||
value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match.
|
||||
if self._ignore_case:
|
||||
value_pattern = re.compile(value, re.IGNORECASE)
|
||||
else:
|
||||
value_pattern = re.compile(value)
|
||||
|
||||
return value_pattern.match(str(metadata[property_name]))
|
||||
|
||||
# Check to see if a container matches with a string
|
||||
def _matchString(self, metadata, property_name, value):
|
||||
if property_name not in metadata:
|
||||
return False
|
||||
return value.lower() == str(metadata[property_name]).lower()
|
|
@ -1,29 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
from cura.UltimakerCloud import UltimakerCloudConstants
|
||||
|
||||
|
||||
class CloudApiModel:
|
||||
sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
|
||||
cloud_api_version = UltimakerCloudConstants.CuraCloudAPIVersion # type: str
|
||||
cloud_api_root = UltimakerCloudConstants.CuraCloudAPIRoot # type: str
|
||||
api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
|
||||
cloud_api_root = cloud_api_root,
|
||||
cloud_api_version = cloud_api_version,
|
||||
sdk_version = sdk_version
|
||||
) # type: str
|
||||
|
||||
# https://api.ultimaker.com/cura-packages/v1/user/packages
|
||||
api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format(
|
||||
cloud_api_root=cloud_api_root,
|
||||
cloud_api_version=cloud_api_version,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def userPackageUrl(cls, package_id: str) -> str:
|
||||
"""https://api.ultimaker.com/cura-packages/v1/user/packages/{package_id}"""
|
||||
|
||||
return (CloudApiModel.api_url_user_packages + "/{package_id}").format(
|
||||
package_id=package_id
|
||||
)
|
|
@ -1,52 +0,0 @@
|
|||
from UM.Logger import Logger
|
||||
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
||||
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||
from ..CloudApiModel import CloudApiModel
|
||||
|
||||
|
||||
class CloudApiClient:
|
||||
"""Manages Cloud subscriptions
|
||||
|
||||
When a package is added to a user's account, the user is 'subscribed' to that package.
|
||||
Whenever the user logs in on another instance of Cura, these subscriptions can be used to sync the user's plugins
|
||||
|
||||
Singleton: use CloudApiClient.getInstance() instead of CloudApiClient()
|
||||
"""
|
||||
|
||||
__instance = None
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls, app: CuraApplication):
|
||||
if not cls.__instance:
|
||||
cls.__instance = CloudApiClient(app)
|
||||
return cls.__instance
|
||||
|
||||
def __init__(self, app: CuraApplication) -> None:
|
||||
if self.__instance is not None:
|
||||
raise RuntimeError("This is a Singleton. use getInstance()")
|
||||
|
||||
self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) # type: JsonDecoratorScope
|
||||
|
||||
app.getPackageManager().packageInstalled.connect(self._onPackageInstalled)
|
||||
|
||||
def unsubscribe(self, package_id: str) -> None:
|
||||
url = CloudApiModel.userPackageUrl(package_id)
|
||||
HttpRequestManager.getInstance().delete(url = url, scope = self._scope)
|
||||
|
||||
def _subscribe(self, package_id: str) -> None:
|
||||
"""You probably don't want to use this directly. All installed packages will be automatically subscribed."""
|
||||
|
||||
Logger.debug("Subscribing to using the Old Toolbox {}", package_id)
|
||||
data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, CloudApiModel.sdk_version)
|
||||
HttpRequestManager.getInstance().put(
|
||||
url = CloudApiModel.api_url_user_packages,
|
||||
data = data.encode(),
|
||||
scope = self._scope
|
||||
)
|
||||
|
||||
def _onPackageInstalled(self, package_id: str):
|
||||
if CuraApplication.getInstance().getCuraAPI().account.isLoggedIn:
|
||||
# We might already be subscribed, but checking would take one extra request. Instead, simply subscribe
|
||||
self._subscribe(package_id)
|
|
@ -1,164 +0,0 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
from typing import List, Dict, Any, Set
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
|
||||
from UM import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
||||
from cura.API.Account import SyncState
|
||||
from cura.CuraApplication import CuraApplication, ApplicationMetadata
|
||||
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
from ..CloudApiModel import CloudApiModel
|
||||
|
||||
|
||||
class CloudPackageChecker(QObject):
|
||||
|
||||
SYNC_SERVICE_NAME = "CloudPackageChecker"
|
||||
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.discrepancies = Signal() # Emits SubscribedPackagesModel
|
||||
self._application = application # type: CuraApplication
|
||||
self._scope = JsonDecoratorScope(UltimakerCloudScope(application))
|
||||
self._model = SubscribedPackagesModel()
|
||||
self._message = None # type: Optional[Message]
|
||||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
self._i18n_catalog = i18nCatalog("cura")
|
||||
self._sdk_version = ApplicationMetadata.CuraSDKVersion
|
||||
self._last_notified_packages = set() # type: Set[str]
|
||||
"""Packages for which a notification has been shown. No need to bother the user twice for equal content"""
|
||||
|
||||
# This is a plugin, so most of the components required are not ready when
|
||||
# this is initialized. Therefore, we wait until the application is ready.
|
||||
def _onAppInitialized(self) -> None:
|
||||
self._package_manager = self._application.getPackageManager()
|
||||
# initial check
|
||||
self._getPackagesIfLoggedIn()
|
||||
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||
self._application.getCuraAPI().account.syncRequested.connect(self._getPackagesIfLoggedIn)
|
||||
|
||||
def _onLoginStateChanged(self) -> None:
|
||||
# reset session
|
||||
self._last_notified_packages = set()
|
||||
self._getPackagesIfLoggedIn()
|
||||
|
||||
def _getPackagesIfLoggedIn(self) -> None:
|
||||
if self._application.getCuraAPI().account.isLoggedIn:
|
||||
self._getUserSubscribedPackages()
|
||||
else:
|
||||
self._hideSyncMessage()
|
||||
|
||||
def _getUserSubscribedPackages(self) -> None:
|
||||
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SYNCING)
|
||||
url = CloudApiModel.api_url_user_packages
|
||||
self._application.getHttpRequestManager().get(url,
|
||||
callback = self._onUserPackagesRequestFinished,
|
||||
error_callback = self._onUserPackagesRequestFinished,
|
||||
timeout=10,
|
||||
scope = self._scope)
|
||||
|
||||
def _onUserPackagesRequestFinished(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None:
|
||||
if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("w",
|
||||
"Requesting user packages failed, response code %s while trying to connect to %s",
|
||||
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
|
||||
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
|
||||
return
|
||||
|
||||
try:
|
||||
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
# Check for errors:
|
||||
if "errors" in json_data:
|
||||
for error in json_data["errors"]:
|
||||
Logger.log("e", "%s", error["title"])
|
||||
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
|
||||
return
|
||||
self._handleCompatibilityData(json_data["data"])
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace")
|
||||
|
||||
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS)
|
||||
|
||||
def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
|
||||
user_subscribed_packages = {plugin["package_id"] for plugin in subscribed_packages_payload}
|
||||
user_installed_packages = self._package_manager.getAllInstalledPackageIDs()
|
||||
|
||||
# We need to re-evaluate the dismissed packages
|
||||
# (i.e. some package might got updated to the correct SDK version in the meantime,
|
||||
# hence remove them from the Dismissed Incompatible list)
|
||||
self._package_manager.reEvaluateDismissedPackages(subscribed_packages_payload, self._sdk_version)
|
||||
user_dismissed_packages = self._package_manager.getDismissedPackages()
|
||||
if user_dismissed_packages:
|
||||
user_installed_packages.update(user_dismissed_packages)
|
||||
|
||||
# We check if there are packages installed in Web Marketplace but not in Cura marketplace
|
||||
package_discrepancy = list(user_subscribed_packages.difference(user_installed_packages))
|
||||
|
||||
if user_subscribed_packages != self._last_notified_packages:
|
||||
# scenario:
|
||||
# 1. user subscribes to a package
|
||||
# 2. dismisses the license/unsubscribes
|
||||
# 3. subscribes to the same package again
|
||||
# in this scenario we want to notify the user again. To capture that there was a change during
|
||||
# step 2, we clear the last_notified after step 2. This way, the user will be notified after
|
||||
# step 3 even though the list of packages for step 1 and 3 are equal
|
||||
self._last_notified_packages = set()
|
||||
|
||||
if package_discrepancy:
|
||||
account = self._application.getCuraAPI().account
|
||||
account.setUpdatePackagesAction(lambda: self._onSyncButtonClicked(None, None))
|
||||
|
||||
if user_subscribed_packages == self._last_notified_packages:
|
||||
# already notified user about these
|
||||
return
|
||||
|
||||
Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
|
||||
self._model.addDiscrepancies(package_discrepancy)
|
||||
self._model.initialize(self._package_manager, subscribed_packages_payload)
|
||||
self._showSyncMessage()
|
||||
self._last_notified_packages = user_subscribed_packages
|
||||
|
||||
def _showSyncMessage(self) -> None:
|
||||
"""Show the message if it is not already shown"""
|
||||
|
||||
if self._message is not None:
|
||||
self._message.show()
|
||||
return
|
||||
|
||||
sync_message = Message(self._i18n_catalog.i18nc(
|
||||
"@info:generic",
|
||||
"Do you want to sync material and software packages with your account?"),
|
||||
title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
|
||||
sync_message.addAction("sync",
|
||||
name = self._i18n_catalog.i18nc("@action:button", "Sync"),
|
||||
icon = "",
|
||||
description = "Sync your plugins and print profiles to Ultimaker Cura.",
|
||||
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT)
|
||||
sync_message.actionTriggered.connect(self._onSyncButtonClicked)
|
||||
sync_message.show()
|
||||
self._message = sync_message
|
||||
|
||||
def _hideSyncMessage(self) -> None:
|
||||
"""Hide the message if it is showing"""
|
||||
|
||||
if self._message is not None:
|
||||
self._message.hide()
|
||||
self._message = None
|
||||
|
||||
def _onSyncButtonClicked(self, sync_message: Optional[Message], sync_message_action: Optional[str]) -> None:
|
||||
if sync_message is not None:
|
||||
sync_message.hide()
|
||||
self._hideSyncMessage() # Should be the same message, but also sets _message to None
|
||||
self.discrepancies.emit(self._model)
|
|
@ -1,41 +0,0 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.Signal import Signal
|
||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
|
||||
|
||||
class DiscrepanciesPresenter(QObject):
|
||||
"""Shows a list of packages to be added or removed. The user can select which packages to (un)install. The user's
|
||||
|
||||
choices are emitted on the `packageMutations` Signal.
|
||||
"""
|
||||
|
||||
def __init__(self, app: QtApplication) -> None:
|
||||
super().__init__(app)
|
||||
|
||||
self.packageMutations = Signal() # Emits SubscribedPackagesModel
|
||||
|
||||
self._app = app
|
||||
self._package_manager = app.getPackageManager()
|
||||
self._dialog = None # type: Optional[QObject]
|
||||
self._compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
|
||||
|
||||
def present(self, plugin_path: str, model: SubscribedPackagesModel) -> None:
|
||||
path = os.path.join(plugin_path, self._compatibility_dialog_path)
|
||||
self._dialog = self._app.createQmlComponent(path, {"subscribedPackagesModel": model, "handler": self})
|
||||
assert self._dialog
|
||||
self._dialog.accepted.connect(lambda: self._onConfirmClicked(model))
|
||||
|
||||
def _onConfirmClicked(self, model: SubscribedPackagesModel) -> None:
|
||||
# If there are incompatible packages - automatically dismiss them
|
||||
if model.getIncompatiblePackages():
|
||||
self._package_manager.dismissAllIncompatiblePackages(model.getIncompatiblePackages())
|
||||
# For now, all compatible packages presented to the user should be installed.
|
||||
# Later, we might remove items for which the user unselected the package
|
||||
if model.getCompatiblePackages():
|
||||
model.setItems(model.getCompatiblePackages())
|
||||
self.packageMutations.emit(model)
|
|
@ -1,153 +0,0 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import tempfile
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class DownloadPresenter:
|
||||
"""Downloads a set of packages from the Ultimaker Cloud Marketplace
|
||||
|
||||
use download() exactly once: should not be used for multiple sets of downloads since this class contains state
|
||||
"""
|
||||
|
||||
DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB
|
||||
|
||||
def __init__(self, app: CuraApplication) -> None:
|
||||
# Emits (Dict[str, str], List[str]) # (success_items, error_items)
|
||||
# Dict{success_package_id, temp_file_path}
|
||||
# List[errored_package_id]
|
||||
self.done = Signal()
|
||||
|
||||
self._app = app
|
||||
self._scope = UltimakerCloudScope(app)
|
||||
|
||||
self._started = False
|
||||
self._progress_message = self._createProgressMessage()
|
||||
self._progress = {} # type: Dict[str, Dict[str, Any]] # package_id, Dict
|
||||
self._error = [] # type: List[str] # package_id
|
||||
|
||||
def download(self, model: SubscribedPackagesModel) -> None:
|
||||
if self._started:
|
||||
Logger.error("Download already started. Create a new %s instead", self.__class__.__name__)
|
||||
return
|
||||
|
||||
manager = HttpRequestManager.getInstance()
|
||||
for item in model.items:
|
||||
package_id = item["package_id"]
|
||||
|
||||
def finishedCallback(reply: QNetworkReply, pid = package_id) -> None:
|
||||
self._onFinished(pid, reply)
|
||||
|
||||
def progressCallback(rx: int, rt: int, pid = package_id) -> None:
|
||||
self._onProgress(pid, rx, rt)
|
||||
|
||||
def errorCallback(reply: QNetworkReply, error: QNetworkReply.NetworkError, pid = package_id) -> None:
|
||||
self._onError(pid)
|
||||
|
||||
request_data = manager.get(
|
||||
item["download_url"],
|
||||
callback = finishedCallback,
|
||||
download_progress_callback = progressCallback,
|
||||
error_callback = errorCallback,
|
||||
scope = self._scope)
|
||||
|
||||
self._progress[package_id] = {
|
||||
"received": 0,
|
||||
"total": 1, # make sure this is not considered done yet. Also divByZero-safe
|
||||
"file_written": None,
|
||||
"request_data": request_data,
|
||||
"package_model": item
|
||||
}
|
||||
|
||||
self._started = True
|
||||
self._progress_message.show()
|
||||
|
||||
def abort(self) -> None:
|
||||
manager = HttpRequestManager.getInstance()
|
||||
for item in self._progress.values():
|
||||
manager.abortRequest(item["request_data"])
|
||||
|
||||
# Aborts all current operations and returns a copy with the same settings such as app and scope
|
||||
def resetCopy(self) -> "DownloadPresenter":
|
||||
self.abort()
|
||||
self.done.disconnectAll()
|
||||
return DownloadPresenter(self._app)
|
||||
|
||||
def _createProgressMessage(self) -> Message:
|
||||
return Message(i18n_catalog.i18nc("@info:generic", "Syncing..."),
|
||||
lifetime = 0,
|
||||
use_inactivity_timer = False,
|
||||
progress = 0.0,
|
||||
title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account"))
|
||||
|
||||
def _onFinished(self, package_id: str, reply: QNetworkReply) -> None:
|
||||
self._progress[package_id]["received"] = self._progress[package_id]["total"]
|
||||
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file:
|
||||
bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE)
|
||||
while bytes_read:
|
||||
temp_file.write(bytes_read)
|
||||
bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE)
|
||||
self._app.processEvents()
|
||||
self._progress[package_id]["file_written"] = temp_file.name
|
||||
except IOError as e:
|
||||
Logger.logException("e", "Failed to write downloaded package to temp file", e)
|
||||
self._onError(package_id)
|
||||
temp_file.close()
|
||||
|
||||
self._checkDone()
|
||||
|
||||
def _onProgress(self, package_id: str, rx: int, rt: int) -> None:
|
||||
self._progress[package_id]["received"] = rx
|
||||
self._progress[package_id]["total"] = rt
|
||||
|
||||
received = 0
|
||||
total = 0
|
||||
for item in self._progress.values():
|
||||
received += item["received"]
|
||||
total += item["total"]
|
||||
|
||||
if total == 0: # Total download size is 0, or unknown, or there are no progress items at all.
|
||||
self._progress_message.setProgress(100.0)
|
||||
return
|
||||
|
||||
self._progress_message.setProgress(100.0 * (received / total)) # [0 .. 100] %
|
||||
|
||||
def _onError(self, package_id: str) -> None:
|
||||
self._progress.pop(package_id)
|
||||
self._error.append(package_id)
|
||||
self._checkDone()
|
||||
|
||||
def _checkDone(self) -> bool:
|
||||
for item in self._progress.values():
|
||||
if not item["file_written"]:
|
||||
return False
|
||||
|
||||
success_items = {
|
||||
package_id:
|
||||
{
|
||||
"package_path": value["file_written"],
|
||||
"icon_url": value["package_model"]["icon_url"]
|
||||
}
|
||||
for package_id, value in self._progress.items()
|
||||
}
|
||||
error_items = [package_id for package_id in self._error]
|
||||
|
||||
self._progress_message.hide()
|
||||
self.done.emit(success_items, error_items)
|
||||
return True
|
|
@ -1,77 +0,0 @@
|
|||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
# Model for the ToolboxLicenseDialog
|
||||
class LicenseModel(QObject):
|
||||
DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline")
|
||||
ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree")
|
||||
|
||||
dialogTitleChanged = pyqtSignal()
|
||||
packageNameChanged = pyqtSignal()
|
||||
licenseTextChanged = pyqtSignal()
|
||||
iconChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._current_page_idx = 0
|
||||
self._page_count = 1
|
||||
self._dialogTitle = ""
|
||||
self._license_text = ""
|
||||
self._package_name = ""
|
||||
self._icon_url = ""
|
||||
self._decline_button_text = decline_button_text
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def acceptButtonText(self):
|
||||
return self.ACCEPT_BUTTON_TEXT
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def declineButtonText(self):
|
||||
return self._decline_button_text
|
||||
|
||||
@pyqtProperty(str, notify=dialogTitleChanged)
|
||||
def dialogTitle(self) -> str:
|
||||
return self._dialogTitle
|
||||
|
||||
@pyqtProperty(str, notify=packageNameChanged)
|
||||
def packageName(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def setPackageName(self, name: str) -> None:
|
||||
self._package_name = name
|
||||
self.packageNameChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=iconChanged)
|
||||
def iconUrl(self) -> str:
|
||||
return self._icon_url
|
||||
|
||||
def setIconUrl(self, url: str):
|
||||
self._icon_url = url
|
||||
self.iconChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=licenseTextChanged)
|
||||
def licenseText(self) -> str:
|
||||
return self._license_text
|
||||
|
||||
def setLicenseText(self, license_text: str) -> None:
|
||||
if self._license_text != license_text:
|
||||
self._license_text = license_text
|
||||
self.licenseTextChanged.emit()
|
||||
|
||||
def setCurrentPageIdx(self, idx: int) -> None:
|
||||
self._current_page_idx = idx
|
||||
self._updateDialogTitle()
|
||||
|
||||
def setPageCount(self, count: int) -> None:
|
||||
self._page_count = count
|
||||
self._updateDialogTitle()
|
||||
|
||||
def _updateDialogTitle(self):
|
||||
self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement")
|
||||
if self._page_count > 1:
|
||||
self._dialogTitle = self._dialogTitle + " ({}/{})".format(self._current_page_idx + 1, self._page_count)
|
||||
self.dialogTitleChanged.emit()
|
|
@ -1,142 +0,0 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, Optional, List, Any
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.PackageManager import PackageManager
|
||||
from UM.Signal import Signal
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from .LicenseModel import LicenseModel
|
||||
|
||||
|
||||
class LicensePresenter(QObject):
|
||||
"""Presents licenses for a set of packages for the user to accept or reject.
|
||||
|
||||
Call present() exactly once to show a licenseDialog for a set of packages
|
||||
Before presenting another set of licenses, create a new instance using resetCopy().
|
||||
|
||||
licenseAnswers emits a list of Dicts containing answers when the user has made a choice for all provided packages.
|
||||
"""
|
||||
|
||||
def __init__(self, app: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
self._presented = False
|
||||
"""Whether present() has been called and state is expected to be initialized"""
|
||||
self._catalog = i18nCatalog("cura")
|
||||
self._dialog = None # type: Optional[QObject]
|
||||
self._package_manager = app.getPackageManager() # type: PackageManager
|
||||
# Emits List[Dict[str, [Any]] containing for example
|
||||
# [{ "package_id": "BarbarianPlugin", "package_path" : "/tmp/dg345as", "accepted" : True }]
|
||||
self.licenseAnswers = Signal()
|
||||
|
||||
self._current_package_idx = 0
|
||||
self._package_models = [] # type: List[Dict]
|
||||
decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account")
|
||||
self._license_model = LicenseModel(decline_button_text=decline_button_text) # type: LicenseModel
|
||||
self._page_count = 0
|
||||
|
||||
self._app = app
|
||||
|
||||
self._compatibility_dialog_path = "resources/qml/dialogs/ToolboxLicenseDialog.qml"
|
||||
|
||||
def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
|
||||
"""Show a license dialog for multiple packages where users can read a license and accept or decline them
|
||||
|
||||
:param plugin_path: Root directory of the Toolbox plugin
|
||||
:param packages: Dict[package id, file path]
|
||||
"""
|
||||
if self._presented:
|
||||
Logger.error("{clazz} is single-use. Create a new {clazz} instead", clazz=self.__class__.__name__)
|
||||
return
|
||||
|
||||
path = os.path.join(plugin_path, self._compatibility_dialog_path)
|
||||
|
||||
self._initState(packages)
|
||||
|
||||
if self._page_count == 0:
|
||||
self.licenseAnswers.emit(self._package_models)
|
||||
return
|
||||
|
||||
if self._dialog is None:
|
||||
|
||||
context_properties = {
|
||||
"catalog": self._catalog,
|
||||
"licenseModel": self._license_model,
|
||||
"handler": self
|
||||
}
|
||||
self._dialog = self._app.createQmlComponent(path, context_properties)
|
||||
self._presentCurrentPackage()
|
||||
self._presented = True
|
||||
|
||||
def resetCopy(self) -> "LicensePresenter":
|
||||
"""Clean up and return a new copy with the same settings such as app"""
|
||||
if self._dialog:
|
||||
self._dialog.close()
|
||||
self.licenseAnswers.disconnectAll()
|
||||
return LicensePresenter(self._app)
|
||||
|
||||
@pyqtSlot()
|
||||
def onLicenseAccepted(self) -> None:
|
||||
self._package_models[self._current_package_idx]["accepted"] = True
|
||||
self._checkNextPage()
|
||||
|
||||
@pyqtSlot()
|
||||
def onLicenseDeclined(self) -> None:
|
||||
self._package_models[self._current_package_idx]["accepted"] = False
|
||||
self._checkNextPage()
|
||||
|
||||
def _initState(self, packages: Dict[str, Dict[str, Any]]) -> None:
|
||||
|
||||
implicitly_accepted_count = 0
|
||||
|
||||
for package_id, item in packages.items():
|
||||
item["package_id"] = package_id
|
||||
try:
|
||||
item["licence_content"] = self._package_manager.getPackageLicense(item["package_path"])
|
||||
except EnvironmentError as e:
|
||||
Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}")
|
||||
continue # Skip this package.
|
||||
if item["licence_content"] is None:
|
||||
# Implicitly accept when there is no license
|
||||
item["accepted"] = True
|
||||
implicitly_accepted_count = implicitly_accepted_count + 1
|
||||
self._package_models.append(item)
|
||||
else:
|
||||
item["accepted"] = None #: None: no answer yet
|
||||
# When presenting the packages, we want to show packages which have a license first.
|
||||
# In fact, we don't want to show the others at all because they are implicitly accepted
|
||||
self._package_models.insert(0, item)
|
||||
CuraApplication.getInstance().processEvents()
|
||||
self._page_count = len(self._package_models) - implicitly_accepted_count
|
||||
self._license_model.setPageCount(self._page_count)
|
||||
|
||||
|
||||
def _presentCurrentPackage(self) -> None:
|
||||
package_model = self._package_models[self._current_package_idx]
|
||||
package_info = self._package_manager.getPackageInfo(package_model["package_path"])
|
||||
|
||||
self._license_model.setCurrentPageIdx(self._current_package_idx)
|
||||
self._license_model.setPackageName(package_info["display_name"])
|
||||
self._license_model.setIconUrl(package_model["icon_url"])
|
||||
self._license_model.setLicenseText(package_model["licence_content"])
|
||||
if self._dialog:
|
||||
self._dialog.open() # Does nothing if already open
|
||||
|
||||
def _checkNextPage(self) -> None:
|
||||
if self._current_package_idx + 1 < self._page_count:
|
||||
self._current_package_idx += 1
|
||||
self._presentCurrentPackage()
|
||||
else:
|
||||
if self._dialog:
|
||||
self._dialog.close()
|
||||
self.licenseAnswers.emit(self._package_models)
|
||||
|
||||
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
from UM import i18nCatalog
|
||||
from UM.Message import Message
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
class RestartApplicationPresenter:
|
||||
"""Presents a dialog telling the user that a restart is required to apply changes
|
||||
|
||||
Since we cannot restart Cura, the app is closed instead when the button is clicked
|
||||
"""
|
||||
def __init__(self, app: CuraApplication) -> None:
|
||||
self._app = app
|
||||
self._i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def present(self) -> None:
|
||||
app_name = self._app.getApplicationDisplayName()
|
||||
|
||||
message = Message(self._i18n_catalog.i18nc("@info:generic",
|
||||
"You need to quit and restart {} before changes have effect.",
|
||||
app_name))
|
||||
|
||||
message.addAction("quit",
|
||||
name="Quit " + app_name,
|
||||
icon = "",
|
||||
description="Close the application",
|
||||
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
|
||||
|
||||
message.actionTriggered.connect(self._quitClicked)
|
||||
message.show()
|
||||
|
||||
def _quitClicked(self, *_):
|
||||
self._app.windowClosed()
|
|
@ -1,74 +0,0 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSlot
|
||||
|
||||
from UM.PackageManager import PackageManager
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Version import Version
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
class SubscribedPackagesModel(ListModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._items = []
|
||||
self._metadata = None
|
||||
self._discrepancies = None
|
||||
self._sdk_version = ApplicationMetadata.CuraSDKVersion
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "package_id")
|
||||
self.addRoleName(Qt.UserRole + 2, "display_name")
|
||||
self.addRoleName(Qt.UserRole + 3, "icon_url")
|
||||
self.addRoleName(Qt.UserRole + 4, "is_compatible")
|
||||
self.addRoleName(Qt.UserRole + 5, "is_dismissed")
|
||||
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def hasCompatiblePackages(self) -> bool:
|
||||
for item in self._items:
|
||||
if item['is_compatible']:
|
||||
return True
|
||||
return False
|
||||
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def hasIncompatiblePackages(self) -> bool:
|
||||
for item in self._items:
|
||||
if not item['is_compatible']:
|
||||
return True
|
||||
return False
|
||||
|
||||
def addDiscrepancies(self, discrepancy: List[str]) -> None:
|
||||
self._discrepancies = discrepancy
|
||||
|
||||
def getCompatiblePackages(self) -> List[Dict[str, Any]]:
|
||||
return [package for package in self._items if package["is_compatible"]]
|
||||
|
||||
def getIncompatiblePackages(self) -> List[str]:
|
||||
return [package["package_id"] for package in self._items if not package["is_compatible"]]
|
||||
|
||||
def initialize(self, package_manager: PackageManager, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
|
||||
self._items.clear()
|
||||
for item in subscribed_packages_payload:
|
||||
if item["package_id"] not in self._discrepancies:
|
||||
continue
|
||||
package = {
|
||||
"package_id": item["package_id"],
|
||||
"display_name": item["display_name"],
|
||||
"sdk_versions": item["sdk_versions"],
|
||||
"download_url": item["download_url"],
|
||||
"md5_hash": item["md5_hash"],
|
||||
"is_dismissed": False,
|
||||
}
|
||||
|
||||
compatible = any(package_manager.isPackageCompatible(Version(version)) for version in item["sdk_versions"])
|
||||
package.update({"is_compatible": compatible})
|
||||
|
||||
try:
|
||||
package.update({"icon_url": item["icon_url"]})
|
||||
except KeyError: # There is no 'icon_url" in the response payload for this package
|
||||
package.update({"icon_url": ""})
|
||||
self._items.append(package)
|
||||
self.setItems(self._items)
|
|
@ -1,114 +0,0 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from typing import List, Dict, Any, cast
|
||||
|
||||
from UM import i18n_catalog
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from .CloudPackageChecker import CloudPackageChecker
|
||||
from .CloudApiClient import CloudApiClient
|
||||
from .DiscrepanciesPresenter import DiscrepanciesPresenter
|
||||
from .DownloadPresenter import DownloadPresenter
|
||||
from .LicensePresenter import LicensePresenter
|
||||
from .RestartApplicationPresenter import RestartApplicationPresenter
|
||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
|
||||
|
||||
class SyncOrchestrator(Extension):
|
||||
"""Orchestrates the synchronizing of packages from the user account to the installed packages
|
||||
|
||||
Example flow:
|
||||
|
||||
- CloudPackageChecker compares a list of packages the user `subscribed` to in their account
|
||||
If there are `discrepancies` between the account and locally installed packages, they are emitted
|
||||
- DiscrepanciesPresenter shows a list of packages to be added or removed to the user. It emits the `packageMutations`
|
||||
the user selected to be performed
|
||||
- The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed
|
||||
- The DownloadPresenter shows a download progress dialog. It emits A tuple of succeeded and failed downloads
|
||||
- The LicensePresenter extracts licenses from the downloaded packages and presents a license for each package to
|
||||
be installed. It emits the `licenseAnswers` signal for accept or declines
|
||||
- The CloudApiClient removes the declined packages from the account
|
||||
- The SyncOrchestrator uses PackageManager to install the downloaded packages and delete temp files.
|
||||
- The RestartApplicationPresenter notifies the user that a restart is required for changes to take effect
|
||||
"""
|
||||
|
||||
def __init__(self, app: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
# Differentiate This PluginObject from the Toolbox. self.getId() includes _name.
|
||||
# getPluginId() will return the same value for The toolbox extension and this one
|
||||
self._name = "SyncOrchestrator"
|
||||
|
||||
self._package_manager = app.getPackageManager()
|
||||
# Keep a reference to the CloudApiClient. it watches for installed packages and subscribes to them
|
||||
self._cloud_api = CloudApiClient.getInstance(app) # type: CloudApiClient
|
||||
|
||||
self._checker = CloudPackageChecker(app) # type: CloudPackageChecker
|
||||
self._checker.discrepancies.connect(self._onDiscrepancies)
|
||||
|
||||
self._discrepancies_presenter = DiscrepanciesPresenter(app) # type: DiscrepanciesPresenter
|
||||
self._discrepancies_presenter.packageMutations.connect(self._onPackageMutations)
|
||||
|
||||
self._download_presenter = DownloadPresenter(app) # type: DownloadPresenter
|
||||
|
||||
self._license_presenter = LicensePresenter(app) # type: LicensePresenter
|
||||
self._license_presenter.licenseAnswers.connect(self._onLicenseAnswers)
|
||||
|
||||
self._restart_presenter = RestartApplicationPresenter(app)
|
||||
|
||||
def _onDiscrepancies(self, model: SubscribedPackagesModel) -> None:
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId()))
|
||||
self._discrepancies_presenter.present(plugin_path, model)
|
||||
|
||||
def _onPackageMutations(self, mutations: SubscribedPackagesModel) -> None:
|
||||
self._download_presenter = self._download_presenter.resetCopy()
|
||||
self._download_presenter.done.connect(self._onDownloadFinished)
|
||||
self._download_presenter.download(mutations)
|
||||
|
||||
def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None:
|
||||
"""Called when a set of packages have finished downloading
|
||||
|
||||
:param success_items:: Dict[package_id, Dict[str, str]]
|
||||
:param error_items:: List[package_id]
|
||||
"""
|
||||
if error_items:
|
||||
message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items)))
|
||||
self._showErrorMessage(message)
|
||||
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId()))
|
||||
self._license_presenter = self._license_presenter.resetCopy()
|
||||
self._license_presenter.licenseAnswers.connect(self._onLicenseAnswers)
|
||||
self._license_presenter.present(plugin_path, success_items)
|
||||
|
||||
# Called when user has accepted / declined all licenses for the downloaded packages
|
||||
def _onLicenseAnswers(self, answers: List[Dict[str, Any]]) -> None:
|
||||
has_changes = False # True when at least one package is installed
|
||||
|
||||
for item in answers:
|
||||
if item["accepted"]:
|
||||
# install and subscribe packages
|
||||
if not self._package_manager.installPackage(item["package_path"]):
|
||||
message = "Could not install {}".format(item["package_id"])
|
||||
self._showErrorMessage(message)
|
||||
continue
|
||||
has_changes = True
|
||||
else:
|
||||
self._cloud_api.unsubscribe(item["package_id"])
|
||||
# delete temp file
|
||||
try:
|
||||
os.remove(item["package_path"])
|
||||
except EnvironmentError as e: # File was already removed, no access rights, etc.
|
||||
Logger.error("Can't delete temporary package file: {err}".format(err = str(e)))
|
||||
|
||||
if has_changes:
|
||||
self._restart_presenter.present()
|
||||
|
||||
def _showErrorMessage(self, text: str):
|
||||
"""Logs an error and shows it to the user"""
|
||||
|
||||
Logger.error(text)
|
||||
Message(text, lifetime = 0, message_type = Message.MessageType.ERROR).show()
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
class ConfigsModel(ListModel):
|
||||
"""Model that holds supported configurations (for material/quality packages)."""
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._configs = None
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "machine")
|
||||
self.addRoleName(Qt.UserRole + 2, "print_core")
|
||||
self.addRoleName(Qt.UserRole + 3, "build_plate")
|
||||
self.addRoleName(Qt.UserRole + 4, "support_material")
|
||||
self.addRoleName(Qt.UserRole + 5, "quality")
|
||||
|
||||
def setConfigs(self, configs):
|
||||
self._configs = configs
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
items = []
|
||||
for item in self._configs:
|
||||
items.append({
|
||||
"machine": item["machine"],
|
||||
"print_core": item["print_core"],
|
||||
"build_plate": item["build_plate"],
|
||||
"support_material": item["support_material"],
|
||||
"quality": item["quality"]
|
||||
})
|
||||
|
||||
self.setItems(items)
|
|
@ -1,161 +0,0 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re
|
||||
from typing import Dict
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtProperty
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from .ConfigsModel import ConfigsModel
|
||||
|
||||
|
||||
class PackagesModel(ListModel):
|
||||
"""Model that holds Cura packages.
|
||||
|
||||
By setting the filter property the instances held by this model can be changed.
|
||||
"""
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._metadata = None
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "id")
|
||||
self.addRoleName(Qt.UserRole + 2, "type")
|
||||
self.addRoleName(Qt.UserRole + 3, "name")
|
||||
self.addRoleName(Qt.UserRole + 4, "version")
|
||||
self.addRoleName(Qt.UserRole + 5, "author_id")
|
||||
self.addRoleName(Qt.UserRole + 6, "author_name")
|
||||
self.addRoleName(Qt.UserRole + 7, "author_email")
|
||||
self.addRoleName(Qt.UserRole + 8, "description")
|
||||
self.addRoleName(Qt.UserRole + 9, "icon_url")
|
||||
self.addRoleName(Qt.UserRole + 10, "image_urls")
|
||||
self.addRoleName(Qt.UserRole + 11, "download_url")
|
||||
self.addRoleName(Qt.UserRole + 12, "last_updated")
|
||||
self.addRoleName(Qt.UserRole + 13, "is_bundled")
|
||||
self.addRoleName(Qt.UserRole + 14, "is_active")
|
||||
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
||||
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
||||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||
self.addRoleName(Qt.UserRole + 19, "tags")
|
||||
self.addRoleName(Qt.UserRole + 20, "links")
|
||||
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.
|
||||
self._filter = {} # type: Dict[str, str]
|
||||
|
||||
def setMetadata(self, data):
|
||||
if self._metadata != data:
|
||||
self._metadata = data
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
items = []
|
||||
|
||||
if self._metadata is None:
|
||||
self.setItems(items)
|
||||
return
|
||||
|
||||
for package in self._metadata:
|
||||
has_configs = False
|
||||
configs_model = None
|
||||
|
||||
links_dict = {}
|
||||
if "data" in package:
|
||||
# Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier
|
||||
# to process.
|
||||
link_list = package["data"]["links"] if "links" in package["data"] else []
|
||||
links_dict = {d["title"]: d["url"] for d in link_list}
|
||||
|
||||
# This code never gets executed because the API response does not contain "supported_configs" in it
|
||||
# It is so because 2y ago when this was created - it did contain it. But it was a prototype only
|
||||
# and never got to production. As agreed with the team, it'll stay here for now, in case we decide to rework and use it
|
||||
# The response payload has been changed. Please see:
|
||||
# https://github.com/Ultimaker/Cura/compare/CURA-7072-temp?expand=1
|
||||
if "supported_configs" in package["data"]:
|
||||
if len(package["data"]["supported_configs"]) > 0:
|
||||
has_configs = True
|
||||
configs_model = ConfigsModel()
|
||||
configs_model.setConfigs(package["data"]["supported_configs"])
|
||||
|
||||
if "author_id" not in package["author"] or "display_name" not in package["author"]:
|
||||
package["author"]["author_id"] = ""
|
||||
package["author"]["display_name"] = ""
|
||||
|
||||
items.append({
|
||||
"id": package["package_id"],
|
||||
"type": package["package_type"],
|
||||
"name": package["display_name"].strip(),
|
||||
"version": package["package_version"],
|
||||
"author_id": package["author"]["author_id"],
|
||||
"author_name": package["author"]["display_name"],
|
||||
"author_email": package["author"]["email"] if "email" in package["author"] else None,
|
||||
"description": package["description"] if "description" in package else None,
|
||||
"icon_url": package["icon_url"] if "icon_url" in package else None,
|
||||
"image_urls": package["image_urls"] if "image_urls" in package else None,
|
||||
"download_url": package["download_url"] if "download_url" in package else None,
|
||||
"last_updated": package["last_updated"] if "last_updated" in package else None,
|
||||
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
|
||||
"is_active": package["is_active"] if "is_active" in package else False,
|
||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||
"has_configs": has_configs,
|
||||
"supported_configs": configs_model,
|
||||
"download_count": package["download_count"] if "download_count" in package else 0,
|
||||
"tags": package["tags"] if "tags" in package else [],
|
||||
"links": links_dict,
|
||||
"website": package["website"] if "website" in package else None,
|
||||
"login_required": "login-required" in package.get("tags", []),
|
||||
})
|
||||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key == "tags":
|
||||
key_filter = lambda item, v = value: v in item["tags"]
|
||||
elif "*" in value:
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v)
|
||||
else:
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v)
|
||||
items = filter(key_filter, items)
|
||||
|
||||
# Execute all filters.
|
||||
filtered_items = list(items)
|
||||
|
||||
filtered_items.sort(key = lambda k: k["name"])
|
||||
self.setItems(filtered_items)
|
||||
|
||||
def setFilter(self, filter_dict: Dict[str, str]) -> None:
|
||||
"""Set the filter of this model based on a string.
|
||||
|
||||
:param filter_dict: Dictionary to do the filtering by.
|
||||
"""
|
||||
if filter_dict != self._filter:
|
||||
self._filter = filter_dict
|
||||
self._update()
|
||||
|
||||
@pyqtProperty("QVariantMap", fset = setFilter, constant = True)
|
||||
def filter(self) -> Dict[str, str]:
|
||||
return self._filter
|
||||
|
||||
# Check to see if a container matches with a regular expression
|
||||
def _matchRegExp(self, metadata, property_name, value):
|
||||
if property_name not in metadata:
|
||||
return False
|
||||
value = re.escape(value) #Escape for regex patterns.
|
||||
value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match.
|
||||
if self._ignore_case:
|
||||
value_pattern = re.compile(value, re.IGNORECASE)
|
||||
else:
|
||||
value_pattern = re.compile(value)
|
||||
|
||||
return value_pattern.match(str(metadata[property_name]))
|
||||
|
||||
# Check to see if a container matches with a string
|
||||
def _matchString(self, metadata, property_name, value):
|
||||
if property_name not in metadata:
|
||||
return False
|
||||
return value.lower() == str(metadata[property_name]).lower()
|
|
@ -1,878 +0,0 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
||||
from UM.Version import Version
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura import ApplicationMetadata
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .CloudApiModel import CloudApiModel
|
||||
from .CloudSync.LicenseModel import LicenseModel
|
||||
from .PackagesModel import PackagesModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.TaskManagement.HttpRequestData import HttpRequestData
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
DEFAULT_MARKETPLACE_ROOT = "https://marketplace.ultimaker.com" # type: str
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraMarketplaceRoot
|
||||
except ImportError:
|
||||
CuraMarketplaceRoot = DEFAULT_MARKETPLACE_ROOT
|
||||
|
||||
|
||||
class Toolbox(QObject, Extension):
|
||||
"""Provides a marketplace for users to download plugins an materials"""
|
||||
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._application = application # type: CuraApplication
|
||||
|
||||
# Network:
|
||||
self._download_request_data = None # type: Optional[HttpRequestData]
|
||||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._cloud_scope = UltimakerCloudScope(application) # type: UltimakerCloudScope
|
||||
self._json_scope = JsonDecoratorScope(self._cloud_scope) # type: JsonDecoratorScope
|
||||
|
||||
self._request_urls = {} # type: Dict[str, str]
|
||||
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = set() # type: Set[str]
|
||||
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
# The responses as given by the server parsed to a list.
|
||||
self._server_response_data = {
|
||||
"authors": [],
|
||||
"packages": [],
|
||||
"updates": []
|
||||
} # type: Dict[str, List[Any]]
|
||||
|
||||
# Models:
|
||||
self._models = {
|
||||
"authors": AuthorsModel(self),
|
||||
"packages": PackagesModel(self),
|
||||
"updates": PackagesModel(self)
|
||||
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
|
||||
|
||||
self._plugins_showcase_model = PackagesModel(self)
|
||||
self._plugins_available_model = PackagesModel(self)
|
||||
self._plugins_installed_model = PackagesModel(self)
|
||||
self._plugins_installed_model.setFilter({"is_bundled": "False"})
|
||||
self._plugins_bundled_model = PackagesModel(self)
|
||||
self._plugins_bundled_model.setFilter({"is_bundled": "True"})
|
||||
self._materials_showcase_model = AuthorsModel(self)
|
||||
self._materials_available_model = AuthorsModel(self)
|
||||
self._materials_installed_model = PackagesModel(self)
|
||||
self._materials_installed_model.setFilter({"is_bundled": "False"})
|
||||
self._materials_bundled_model = PackagesModel(self)
|
||||
self._materials_bundled_model.setFilter({"is_bundled": "True"})
|
||||
self._materials_generic_model = PackagesModel(self)
|
||||
|
||||
self._license_model = LicenseModel()
|
||||
|
||||
# These properties are for keeping track of the UI state:
|
||||
# ----------------------------------------------------------------------
|
||||
# View category defines which filter to use, and therefore effectively
|
||||
# which category is currently being displayed. For example, possible
|
||||
# values include "plugin" or "material", but also "installed".
|
||||
self._view_category = "plugin" # type: str
|
||||
|
||||
# View page defines which type of page layout to use. For example,
|
||||
# possible values include "overview", "detail" or "author".
|
||||
self._view_page = "welcome" # type: str
|
||||
|
||||
# Active package refers to which package is currently being downloaded,
|
||||
# installed, or otherwise modified.
|
||||
self._active_package = None # type: Optional[Dict[str, Any]]
|
||||
|
||||
self._dialog = None # type: Optional[QObject]
|
||||
self._confirm_reset_dialog = None # type: Optional[QObject]
|
||||
self._resetUninstallVariables()
|
||||
|
||||
self._restart_required = False # type: bool
|
||||
|
||||
# variables for the license agreement dialog
|
||||
self._license_dialog_plugin_file_location = "" # type: str
|
||||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
|
||||
# Signals:
|
||||
# --------------------------------------------------------------------------
|
||||
# Downloading changes
|
||||
activePackageChanged = pyqtSignal()
|
||||
onDownloadProgressChanged = pyqtSignal()
|
||||
onIsDownloadingChanged = pyqtSignal()
|
||||
restartRequiredChanged = pyqtSignal()
|
||||
installChanged = pyqtSignal()
|
||||
toolboxEnabledChanged = pyqtSignal()
|
||||
|
||||
# UI changes
|
||||
viewChanged = pyqtSignal()
|
||||
detailViewChanged = pyqtSignal()
|
||||
filterChanged = pyqtSignal()
|
||||
metadataChanged = pyqtSignal()
|
||||
showLicenseDialog = pyqtSignal()
|
||||
closeLicenseDialog = pyqtSignal()
|
||||
uninstallVariablesChanged = pyqtSignal()
|
||||
|
||||
def _restart(self):
|
||||
"""Go back to the start state (welcome screen or loading if no login required)"""
|
||||
|
||||
# For an Essentials build, login is mandatory
|
||||
if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion:
|
||||
self.setViewPage("welcome")
|
||||
else:
|
||||
self.setViewPage("loading")
|
||||
self._fetchPackageData()
|
||||
|
||||
def _resetUninstallVariables(self) -> None:
|
||||
self._package_id_to_uninstall = None # type: Optional[str]
|
||||
self._package_name_to_uninstall = ""
|
||||
self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
|
||||
def getLicenseDialogPluginFileLocation(self) -> str:
|
||||
return self._license_dialog_plugin_file_location
|
||||
|
||||
def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None:
|
||||
# Set page 1/1 when opening the dialog for a single package
|
||||
self._license_model.setCurrentPageIdx(0)
|
||||
self._license_model.setPageCount(1)
|
||||
self._license_model.setIconUrl(icon_url)
|
||||
|
||||
self._license_model.setPackageName(plugin_name)
|
||||
self._license_model.setLicenseText(license_content)
|
||||
self._license_dialog_plugin_file_location = plugin_file_location
|
||||
self.showLicenseDialog.emit()
|
||||
|
||||
# This is a plugin, so most of the components required are not ready when
|
||||
# this is initialized. Therefore, we wait until the application is ready.
|
||||
def _onAppInitialized(self) -> None:
|
||||
self._plugin_registry = self._application.getPluginRegistry()
|
||||
self._package_manager = self._application.getPackageManager()
|
||||
|
||||
# We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc.
|
||||
installed_package_ids_with_versions = [":".join(items) for items in
|
||||
self._package_manager.getAllInstalledPackageIdsAndVersions()]
|
||||
installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions)
|
||||
|
||||
self._request_urls = {
|
||||
"authors": "{base_url}/authors".format(base_url = CloudApiModel.api_url),
|
||||
"packages": "{base_url}/packages".format(base_url = CloudApiModel.api_url),
|
||||
"updates": "{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = CloudApiModel.api_url, query = installed_packages_query)
|
||||
}
|
||||
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
|
||||
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
|
||||
preferences.addPreference("info/automatic_plugin_update_check", True)
|
||||
|
||||
# On boot we check which packages have updates.
|
||||
if preferences.getValue("info/automatic_plugin_update_check") and len(installed_package_ids_with_versions) > 0:
|
||||
# Request the latest and greatest!
|
||||
self._makeRequestByType("updates")
|
||||
|
||||
def _fetchPackageData(self) -> None:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
self._updateInstalledModels()
|
||||
|
||||
# Displays the toolbox
|
||||
@pyqtSlot()
|
||||
def launch(self) -> None:
|
||||
if not self._dialog:
|
||||
self._dialog = self._createDialog("Toolbox.qml")
|
||||
|
||||
if not self._dialog:
|
||||
Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.")
|
||||
return
|
||||
|
||||
self._restart()
|
||||
|
||||
self._dialog.show()
|
||||
# Apply enabled/disabled state to installed plugins
|
||||
self.toolboxEnabledChanged.emit()
|
||||
|
||||
def _createDialog(self, qml_name: str) -> Optional[QObject]:
|
||||
Logger.log("d", "Marketplace: Creating dialog [%s].", qml_name)
|
||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||
if not plugin_path:
|
||||
return None
|
||||
path = os.path.join(plugin_path, "resources", "qml", qml_name)
|
||||
|
||||
dialog = self._application.createQmlComponent(path, {
|
||||
"toolbox": self,
|
||||
"handler": self,
|
||||
"licenseModel": self._license_model
|
||||
})
|
||||
if not dialog:
|
||||
return None
|
||||
return dialog
|
||||
|
||||
def _convertPluginMetadata(self, plugin_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
highest_sdk_version_supported = Version(0)
|
||||
for supported_version in plugin_data["plugin"]["supported_sdk_versions"]:
|
||||
if supported_version > highest_sdk_version_supported:
|
||||
highest_sdk_version_supported = supported_version
|
||||
|
||||
formatted = {
|
||||
"package_id": plugin_data["id"],
|
||||
"package_type": "plugin",
|
||||
"display_name": plugin_data["plugin"]["name"],
|
||||
"package_version": plugin_data["plugin"]["version"],
|
||||
"sdk_version": highest_sdk_version_supported,
|
||||
"author": {
|
||||
"author_id": plugin_data["plugin"]["author"],
|
||||
"display_name": plugin_data["plugin"]["author"]
|
||||
},
|
||||
"is_installed": True,
|
||||
"description": plugin_data["plugin"]["description"]
|
||||
}
|
||||
return formatted
|
||||
except KeyError:
|
||||
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
|
||||
return None
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateInstalledModels(self) -> None:
|
||||
# This is moved here to avoid code duplication and so that after installing plugins they get removed from the
|
||||
# list of old plugins
|
||||
old_plugin_ids = self._plugin_registry.getInstalledPlugins()
|
||||
installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
|
||||
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
|
||||
|
||||
self._old_plugin_ids = set()
|
||||
self._old_plugin_metadata = dict()
|
||||
|
||||
for plugin_id in old_plugin_ids:
|
||||
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
||||
if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
|
||||
Logger.log("d", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
|
||||
|
||||
old_metadata = self._plugin_registry.getMetaData(plugin_id)
|
||||
new_metadata = self._convertPluginMetadata(old_metadata)
|
||||
if new_metadata is None:
|
||||
# Something went wrong converting it.
|
||||
continue
|
||||
self._old_plugin_ids.add(plugin_id)
|
||||
self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata
|
||||
|
||||
all_packages = self._package_manager.getAllInstalledPackagesInfo()
|
||||
if "plugin" in all_packages:
|
||||
# For old plugins, we only want to include the old custom plugin that were installed via the old toolbox.
|
||||
# The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled
|
||||
# plugins should be excluded from the old plugins list/dict.
|
||||
all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"])
|
||||
self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_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._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
|
||||
self._plugins_bundled_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
|
||||
self.metadataChanged.emit()
|
||||
if "material" in all_packages:
|
||||
self._materials_installed_model.setMetadata(all_packages["material"])
|
||||
self._materials_bundled_model.setMetadata(all_packages["material"])
|
||||
self.metadataChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def install(self, file_path: str) -> Optional[str]:
|
||||
package_id = self._package_manager.installPackage(file_path)
|
||||
self.installChanged.emit()
|
||||
self._updateInstalledModels()
|
||||
self.metadataChanged.emit()
|
||||
self._restart_required = True
|
||||
self.restartRequiredChanged.emit()
|
||||
return package_id
|
||||
|
||||
@pyqtSlot(str)
|
||||
def checkPackageUsageAndUninstall(self, package_id: str) -> None:
|
||||
"""Check package usage and uninstall
|
||||
|
||||
If the package is in use, you'll get a confirmation dialog to set everything to default
|
||||
"""
|
||||
|
||||
package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id)
|
||||
if package_used_materials or package_used_qualities:
|
||||
# Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall
|
||||
self._package_id_to_uninstall = package_id
|
||||
package_info = self._package_manager.getInstalledPackageInfo(package_id)
|
||||
self._package_name_to_uninstall = package_info.get("display_name", package_info.get("package_id"))
|
||||
self._package_used_materials = package_used_materials
|
||||
self._package_used_qualities = package_used_qualities
|
||||
# Ask change to default material / profile
|
||||
if self._confirm_reset_dialog is None:
|
||||
self._confirm_reset_dialog = self._createDialog("dialogs/ToolboxConfirmUninstallResetDialog.qml")
|
||||
self.uninstallVariablesChanged.emit()
|
||||
if self._confirm_reset_dialog is None:
|
||||
Logger.log("e", "ToolboxConfirmUninstallResetDialog should have been initialized, but it is not. Not showing dialog and not uninstalling package.")
|
||||
else:
|
||||
self._confirm_reset_dialog.show()
|
||||
else:
|
||||
# Plain uninstall
|
||||
self.uninstall(package_id)
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def pluginToUninstall(self) -> str:
|
||||
return self._package_name_to_uninstall
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedMaterials(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedQualities(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
|
||||
|
||||
@pyqtSlot()
|
||||
def closeConfirmResetDialog(self) -> None:
|
||||
if self._confirm_reset_dialog is not None:
|
||||
self._confirm_reset_dialog.close()
|
||||
|
||||
@pyqtSlot()
|
||||
def resetMaterialsQualitiesAndUninstall(self) -> None:
|
||||
"""Uses "uninstall variables" to reset qualities and materials, then uninstall
|
||||
|
||||
It's used as an action on Confirm reset on Uninstall
|
||||
"""
|
||||
|
||||
application = CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
for global_stack, extruder_nr, container_id in self._package_used_materials:
|
||||
extruder = global_stack.extruderList[int(extruder_nr)]
|
||||
approximate_diameter = extruder.getApproximateMaterialDiameter()
|
||||
variant_node = container_tree.machines[global_stack.definition.getId()].variants[extruder.variant.getName()]
|
||||
default_material_node = variant_node.preferredMaterial(approximate_diameter)
|
||||
machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
|
||||
for global_stack, extruder_nr, container_id in self._package_used_qualities:
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
definition_id = global_stack.definition.getId()
|
||||
machine_node = container_tree.machines[definition_id]
|
||||
default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type]
|
||||
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
|
||||
|
||||
if self._package_id_to_uninstall is not None:
|
||||
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
|
||||
self.uninstall(self._package_id_to_uninstall)
|
||||
self._resetUninstallVariables()
|
||||
self.closeConfirmResetDialog()
|
||||
|
||||
@pyqtSlot()
|
||||
def onLicenseAccepted(self):
|
||||
self.closeLicenseDialog.emit()
|
||||
package_id = self.install(self.getLicenseDialogPluginFileLocation())
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def onLicenseDeclined(self):
|
||||
self.closeLicenseDialog.emit()
|
||||
|
||||
def _markPackageMaterialsAsToBeUninstalled(self, package_id: str) -> None:
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
|
||||
all_containers = self._package_manager.getPackageContainerIds(package_id)
|
||||
for container_id in all_containers:
|
||||
containers = container_registry.findInstanceContainers(id = container_id)
|
||||
if not containers:
|
||||
continue
|
||||
container = containers[0]
|
||||
if container.getMetaDataEntry("type") != "material":
|
||||
continue
|
||||
root_material_id = container.getMetaDataEntry("base_file")
|
||||
root_material_containers = container_registry.findInstanceContainers(id = root_material_id)
|
||||
if not root_material_containers:
|
||||
continue
|
||||
root_material_container = root_material_containers[0]
|
||||
root_material_container.setMetaDataEntry("removed", True)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def uninstall(self, package_id: str) -> None:
|
||||
self._package_manager.removePackage(package_id, force_add = True)
|
||||
self.installChanged.emit()
|
||||
self._updateInstalledModels()
|
||||
self.metadataChanged.emit()
|
||||
self._restart_required = True
|
||||
self.restartRequiredChanged.emit()
|
||||
|
||||
def _update(self) -> None:
|
||||
"""Actual update packages that are in self._to_update"""
|
||||
|
||||
if self._to_update:
|
||||
plugin_id = self._to_update.pop(0)
|
||||
remote_package = self.getRemotePackage(plugin_id)
|
||||
if remote_package:
|
||||
download_url = remote_package["download_url"]
|
||||
Logger.log("d", "Updating package [%s]..." % plugin_id)
|
||||
self.startDownload(download_url)
|
||||
else:
|
||||
Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id)
|
||||
|
||||
if self._to_update:
|
||||
self._application.callLater(self._update)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def update(self, plugin_id: str) -> None:
|
||||
"""Update a plugin by plugin_id"""
|
||||
|
||||
self._to_update.append(plugin_id)
|
||||
self._application.callLater(self._update)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def enable(self, plugin_id: str) -> None:
|
||||
self._plugin_registry.enablePlugin(plugin_id)
|
||||
self.toolboxEnabledChanged.emit()
|
||||
Logger.log("i", "%s was set as 'active'.", plugin_id)
|
||||
self._restart_required = True
|
||||
self.restartRequiredChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def disable(self, plugin_id: str) -> None:
|
||||
self._plugin_registry.disablePlugin(plugin_id)
|
||||
self.toolboxEnabledChanged.emit()
|
||||
Logger.log("i", "%s was set as 'deactive'.", plugin_id)
|
||||
self._restart_required = True
|
||||
self.restartRequiredChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = metadataChanged)
|
||||
def dataReady(self) -> bool:
|
||||
return self._packages_model is not None
|
||||
|
||||
@pyqtProperty(bool, notify = restartRequiredChanged)
|
||||
def restartRequired(self) -> bool:
|
||||
return self._restart_required
|
||||
|
||||
@pyqtSlot()
|
||||
def restart(self) -> None:
|
||||
self._application.windowClosed()
|
||||
|
||||
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
||||
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
||||
remote_package = None
|
||||
for package in self._server_response_data["packages"]:
|
||||
if package["package_id"] == package_id:
|
||||
remote_package = package
|
||||
break
|
||||
return remote_package
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def canDowngrade(self, package_id: str) -> bool:
|
||||
# If the currently installed version is higher than the bundled version (if present), the we can downgrade
|
||||
# this package.
|
||||
local_package = self._package_manager.getInstalledPackageInfo(package_id)
|
||||
if local_package is None:
|
||||
return False
|
||||
|
||||
bundled_package = self._package_manager.getBundledPackageInfo(package_id)
|
||||
if bundled_package is None:
|
||||
return False
|
||||
|
||||
local_version = Version(local_package["package_version"])
|
||||
bundled_version = Version(bundled_package["package_version"])
|
||||
return bundled_version < local_version
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isInstalled(self, package_id: str) -> bool:
|
||||
result = self._package_manager.isPackageInstalled(package_id)
|
||||
# Also check the old plugins list if it's not found in the package manager.
|
||||
if not result:
|
||||
result = self.isOldPlugin(package_id)
|
||||
return result
|
||||
|
||||
@pyqtSlot(str, result = int)
|
||||
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
|
||||
count = 0
|
||||
for package in self._materials_installed_model.items:
|
||||
if package["author_id"] == author_id:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
# This slot is only used to get the number of material packages by author, not any other type of packages.
|
||||
@pyqtSlot(str, result = int)
|
||||
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
|
||||
count = 0
|
||||
for package in self._server_response_data["packages"]:
|
||||
if package["package_type"] == "material":
|
||||
if package["author"]["author_id"] == author_id:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isEnabled(self, package_id: str) -> bool:
|
||||
return package_id in self._plugin_registry.getActivePlugins()
|
||||
|
||||
# Check for plugins that were installed with the old plugin browser
|
||||
def isOldPlugin(self, plugin_id: str) -> bool:
|
||||
return plugin_id in self._old_plugin_ids
|
||||
|
||||
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
|
||||
return self._old_plugin_metadata.get(plugin_id)
|
||||
|
||||
def isLoadingComplete(self) -> bool:
|
||||
populated = 0
|
||||
for metadata_list in self._server_response_data.items():
|
||||
if metadata_list:
|
||||
populated += 1
|
||||
return populated == len(self._server_response_data.items())
|
||||
|
||||
# Make API Calls
|
||||
# --------------------------------------------------------------------------
|
||||
def _makeRequestByType(self, request_type: str) -> None:
|
||||
Logger.debug(f"Requesting {request_type} metadata from server.")
|
||||
url = self._request_urls[request_type]
|
||||
|
||||
callback = lambda r, rt = request_type: self._onDataRequestFinished(rt, r)
|
||||
error_callback = lambda r, e, rt = request_type: self._onDataRequestError(rt, r, e)
|
||||
self._application.getHttpRequestManager().get(url,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
scope=self._json_scope)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def startDownload(self, url: str) -> None:
|
||||
Logger.info(f"Attempting to download & install package from {url}.")
|
||||
|
||||
callback = lambda r: self._onDownloadFinished(r)
|
||||
error_callback = lambda r, e: self._onDownloadFailed(r, e)
|
||||
download_progress_callback = self._onDownloadProgress
|
||||
request_data = self._application.getHttpRequestManager().get(url,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback,
|
||||
scope=self._cloud_scope
|
||||
)
|
||||
|
||||
self._download_request_data = request_data
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelDownload(self) -> None:
|
||||
Logger.info(f"User cancelled the download of a package. request {self._download_request_data}")
|
||||
if self._download_request_data is not None:
|
||||
self._application.getHttpRequestManager().abortRequest(self._download_request_data)
|
||||
self._download_request_data = None
|
||||
self.resetDownload()
|
||||
|
||||
def resetDownload(self) -> None:
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(False)
|
||||
|
||||
# Handlers for Network Events
|
||||
# --------------------------------------------------------------------------
|
||||
def _onDataRequestError(self, request_type: str, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.error(f"Request {request_type} failed due to error {error}: {reply.errorString()}")
|
||||
self.setViewPage("errored")
|
||||
|
||||
def _onDataRequestFinished(self, request_type: str, reply: "QNetworkReply") -> None:
|
||||
if reply.operation() != QNetworkAccessManager.GetOperation:
|
||||
Logger.log("e", "_onDataRequestFinished() only handles GET requests but got [%s] instead", reply.operation())
|
||||
return
|
||||
|
||||
http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if http_status_code != 200:
|
||||
Logger.log("e", "Request type [%s] got non-200 HTTP response: [%s]", http_status_code)
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
data = bytes(reply.readAll())
|
||||
try:
|
||||
json_data = json.loads(data.decode("utf-8"))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("e", "Failed to decode response data as JSON for request type [%s], response data [%s]",
|
||||
request_type, data)
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
# Check for errors:
|
||||
if "errors" in json_data:
|
||||
for error in json_data["errors"]:
|
||||
Logger.log("e", "Request type [%s] got response showing error: %s", error.get("title", "No error title found"))
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
# Create model and apply metadata:
|
||||
if not self._models[request_type]:
|
||||
Logger.log("e", "Could not find the model for request type [%s].", request_type)
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
self._server_response_data[request_type] = json_data["data"]
|
||||
self._models[request_type].setMetadata(self._server_response_data[request_type])
|
||||
|
||||
if request_type == "packages":
|
||||
self._models[request_type].setFilter({"type": "plugin"})
|
||||
self.reBuildMaterialsModels()
|
||||
self.reBuildPluginsModels()
|
||||
self._notifyPackageManager()
|
||||
elif request_type == "authors":
|
||||
self._models[request_type].setFilter({"package_types": "material"})
|
||||
self._models[request_type].setFilter({"tags": "generic"})
|
||||
elif request_type == "updates":
|
||||
# Tell the package manager that there's a new set of updates available.
|
||||
packages = self._server_response_data[request_type]
|
||||
self._package_manager.setPackagesWithUpdate({p['package_id'] for p in packages})
|
||||
|
||||
self.metadataChanged.emit()
|
||||
|
||||
if self.isLoadingComplete():
|
||||
self.setViewPage("overview")
|
||||
|
||||
# This function goes through all known remote versions of a package and notifies the package manager of this change
|
||||
def _notifyPackageManager(self):
|
||||
for package in self._server_response_data["packages"]:
|
||||
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
|
||||
|
||||
def _onDownloadFinished(self, reply: "QNetworkReply") -> None:
|
||||
self.resetDownload()
|
||||
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
try:
|
||||
reply_error = json.loads(reply.readAll().data().decode("utf-8"))
|
||||
except Exception as e:
|
||||
reply_error = str(e)
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", reply_error)
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(reply.readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
def _onDownloadFailed(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", error)
|
||||
|
||||
self.resetDownload()
|
||||
|
||||
def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
self.setDownloadProgress(new_progress)
|
||||
Logger.log("d", "new download progress %s / %s : %s%%", bytes_sent, bytes_total, new_progress)
|
||||
|
||||
def _onDownloadComplete(self, file_path: str) -> None:
|
||||
Logger.log("i", "Download complete.")
|
||||
package_info = self._package_manager.getPackageInfo(file_path)
|
||||
if not package_info:
|
||||
Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
|
||||
return
|
||||
package_id = package_info["package_id"]
|
||||
|
||||
try:
|
||||
license_content = self._package_manager.getPackageLicense(file_path)
|
||||
except EnvironmentError as e:
|
||||
Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}")
|
||||
return
|
||||
if license_content is not None:
|
||||
# get the icon url for package_id, make sure the result is a string, never None
|
||||
icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or ""
|
||||
self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url)
|
||||
return
|
||||
|
||||
installed_id = self.install(file_path)
|
||||
if installed_id != package_id:
|
||||
Logger.error("Installed package {} does not match {}".format(installed_id, package_id))
|
||||
|
||||
# Getter & Setters for Properties:
|
||||
# --------------------------------------------------------------------------
|
||||
def setDownloadProgress(self, progress: float) -> None:
|
||||
if progress != self._download_progress:
|
||||
self._download_progress = progress
|
||||
self.onDownloadProgressChanged.emit()
|
||||
|
||||
@pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
|
||||
def downloadProgress(self) -> float:
|
||||
return self._download_progress
|
||||
|
||||
def setIsDownloading(self, is_downloading: bool) -> None:
|
||||
if self._is_downloading != is_downloading:
|
||||
self._is_downloading = is_downloading
|
||||
self.onIsDownloadingChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, fset = setIsDownloading, notify = onIsDownloadingChanged)
|
||||
def isDownloading(self) -> bool:
|
||||
return self._is_downloading
|
||||
|
||||
def setActivePackage(self, package: QObject) -> None:
|
||||
if self._active_package != package:
|
||||
self._active_package = package
|
||||
self.activePackageChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
|
||||
def activePackage(self) -> Optional[QObject]:
|
||||
"""The active package is the package that is currently being downloaded"""
|
||||
|
||||
return self._active_package
|
||||
|
||||
def setViewCategory(self, category: str = "plugin") -> None:
|
||||
if self._view_category != category:
|
||||
self._view_category = category
|
||||
self.viewChanged.emit()
|
||||
|
||||
# Function explicitly defined so that it can be called through the callExtensionsMethod
|
||||
# which cannot receive arguments.
|
||||
def setViewCategoryToMaterials(self) -> None:
|
||||
self.setViewCategory("material")
|
||||
|
||||
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
|
||||
def viewCategory(self) -> str:
|
||||
return self._view_category
|
||||
|
||||
def setViewPage(self, page: str = "overview") -> None:
|
||||
if self._view_page != page:
|
||||
self._view_page = page
|
||||
self.viewChanged.emit()
|
||||
|
||||
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
|
||||
def viewPage(self) -> str:
|
||||
return self._view_page
|
||||
|
||||
# Exposed Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def authorsModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["authors"])
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def packagesModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["packages"])
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsShowcaseModel(self) -> PackagesModel:
|
||||
return self._plugins_showcase_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsAvailableModel(self) -> PackagesModel:
|
||||
return self._plugins_available_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsInstalledModel(self) -> PackagesModel:
|
||||
return self._plugins_installed_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsBundledModel(self) -> PackagesModel:
|
||||
return self._plugins_bundled_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsShowcaseModel(self) -> AuthorsModel:
|
||||
return self._materials_showcase_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsAvailableModel(self) -> AuthorsModel:
|
||||
return self._materials_available_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsInstalledModel(self) -> PackagesModel:
|
||||
return self._materials_installed_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsBundledModel(self) -> PackagesModel:
|
||||
return self._materials_bundled_model
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsGenericModel(self) -> PackagesModel:
|
||||
return self._materials_generic_model
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getWebMarketplaceUrl(self, page: str) -> str:
|
||||
root = CuraMarketplaceRoot
|
||||
if root == "":
|
||||
root = DEFAULT_MARKETPLACE_ROOT
|
||||
return root + "/app/cura/" + page
|
||||
|
||||
# Filter Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtSlot(str, str, str)
|
||||
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
|
||||
if not self._models[model_type]:
|
||||
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
|
||||
return
|
||||
self._models[model_type].setFilter({filter_type: parameter})
|
||||
self.filterChanged.emit()
|
||||
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
def setFilters(self, model_type: str, filter_dict: dict) -> None:
|
||||
if not self._models[model_type]:
|
||||
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
|
||||
return
|
||||
self._models[model_type].setFilter(filter_dict)
|
||||
self.filterChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def removeFilters(self, model_type: str) -> None:
|
||||
if not self._models[model_type]:
|
||||
Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
|
||||
return
|
||||
self._models[model_type].setFilter({})
|
||||
self.filterChanged.emit()
|
||||
|
||||
# HACK(S):
|
||||
# --------------------------------------------------------------------------
|
||||
def reBuildMaterialsModels(self) -> None:
|
||||
materials_showcase_metadata = []
|
||||
materials_available_metadata = []
|
||||
materials_generic_metadata = []
|
||||
|
||||
processed_authors = [] # type: List[str]
|
||||
|
||||
for item in self._server_response_data["packages"]:
|
||||
if item["package_type"] == "material":
|
||||
|
||||
author = item["author"]
|
||||
if author["author_id"] in processed_authors:
|
||||
continue
|
||||
|
||||
# Generic materials to be in the same section
|
||||
if "generic" in item["tags"]:
|
||||
materials_generic_metadata.append(item)
|
||||
else:
|
||||
if "showcase" in item["tags"]:
|
||||
materials_showcase_metadata.append(author)
|
||||
else:
|
||||
materials_available_metadata.append(author)
|
||||
|
||||
processed_authors.append(author["author_id"])
|
||||
|
||||
self._materials_showcase_model.setMetadata(materials_showcase_metadata)
|
||||
self._materials_available_model.setMetadata(materials_available_metadata)
|
||||
self._materials_generic_model.setMetadata(materials_generic_metadata)
|
||||
|
||||
def reBuildPluginsModels(self) -> None:
|
||||
plugins_showcase_metadata = []
|
||||
plugins_available_metadata = []
|
||||
|
||||
for item in self._server_response_data["packages"]:
|
||||
if item["package_type"] == "plugin":
|
||||
if "showcase" in item["tags"]:
|
||||
plugins_showcase_metadata.append(item)
|
||||
else:
|
||||
plugins_available_metadata.append(item)
|
||||
|
||||
self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
|
||||
self._plugins_available_model.setMetadata(plugins_available_metadata)
|
|
@ -526,13 +526,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Toolbox": {
|
||||
"Marketplace": {
|
||||
"package_info": {
|
||||
"package_id": "Toolbox",
|
||||
"package_id": "Marketplace",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Toolbox",
|
||||
"display_name": "Marketplace",
|
||||
"description": "Find, manage and install new Cura packages.",
|
||||
"package_version": "1.0.1",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": "7.8.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
|
|
|
@ -196,15 +196,7 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
// show the Toolbox
|
||||
Connections
|
||||
{
|
||||
target: Cura.Actions.browsePackages
|
||||
function onTriggered()
|
||||
{
|
||||
curaExtensions.callExtensionMethod("Toolbox", "launch")
|
||||
}
|
||||
}
|
||||
// show the Marketplace
|
||||
Connections
|
||||
{
|
||||
target: Cura.Actions.openMarketplace
|
||||
|
|
|
@ -83,53 +83,6 @@ Item
|
|||
ExclusiveGroup { id: mainWindowHeaderMenuGroup }
|
||||
}
|
||||
|
||||
// Shortcut button to quick access the Toolbox
|
||||
Controls2.Button //TODO: Remove once new Marketplace is completed.
|
||||
{
|
||||
text: "Old Marketplace"
|
||||
height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height)
|
||||
onClicked: Cura.Actions.browsePackages.trigger()
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
id: marketplaceButtonBorder
|
||||
radius: UM.Theme.getSize("action_button_radius").width
|
||||
color: UM.Theme.getColor("main_window_header_background")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("primary_text")
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: marketplaceButtonFill
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: UM.Theme.getColor("primary_text")
|
||||
opacity: parent.hovered ? 0.2 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
id: label
|
||||
text: parent.text
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("primary_text")
|
||||
width: contentWidth
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
anchors
|
||||
{
|
||||
right: marketplaceButton.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Controls2.Button
|
||||
{
|
||||
id: marketplaceButton
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
|
|
@ -8,7 +8,7 @@ Run this file with the Cura project root as the working directory
|
|||
Checks for invalid imports. When importing from plugins, there will be no problems when running from source,
|
||||
but for some build types the plugins dir is not on the path, so relative imports should be used instead. eg:
|
||||
from ..UltimakerCloudScope import UltimakerCloudScope <-- OK
|
||||
import plugins.Toolbox.src ... <-- NOT OK
|
||||
import plugins.Marketplace.src ... <-- NOT OK
|
||||
"""
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue