Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
HellAholic d586fddf4b
Change text wrap mode to NoWrap
no wrapping as it will cause issues in gcode context
2026-01-13 21:13:58 +01:00

622 lines
24 KiB
QML

// Copyright (c) 2022 Jaime van Kessel, Ultimaker B.V.
// The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQml.Models 2.15 as Models
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import UM 1.5 as UM
import Cura 1.0 as Cura
UM.Dialog
{
id: dialog
title: catalog.i18nc("@title:window", "Post Processing Plugin")
width: 700 * screenScaleFactor
height: 500 * screenScaleFactor
minimumWidth: 400 * screenScaleFactor
minimumHeight: 250 * screenScaleFactor
backgroundColor: UM.Theme.getColor("main_background")
onVisibleChanged:
{
// Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
if (!visible)
{
manager.writeScriptsToStack()
}
}
Item
{
UM.I18nCatalog{id: catalog; name: "cura"}
id: base
property int columnWidth: Math.round((base.width / 2) - UM.Theme.getSize("default_margin").width)
property int textMargin: UM.Theme.getSize("narrow_margin").width
property string activeScriptName
anchors.fill: parent
ButtonGroup
{
id: selectedScriptGroup
}
Column
{
id: activeScripts
width: base.columnWidth
height: parent.height
spacing: base.textMargin
UM.Label
{
id: activeScriptsHeader
text: catalog.i18nc("@label", "Post Processing Scripts")
anchors.left: parent.left
anchors.right: parent.right
font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight
}
ListView
{
id: activeScriptsList
anchors
{
left: parent.left
right: parent.right
rightMargin: base.textMargin
}
height: Math.min(contentHeight, parent.height - parent.spacing * 2 - activeScriptsHeader.height - addButton.height) //At the window height, start scrolling this one.
clip: true
ScrollBar.vertical: UM.ScrollBar
{
id: activeScriptsScrollBar
}
model: manager.scriptList
delegate: Button
{
id: activeScriptButton
width: parent.width - activeScriptsScrollBar.width
height: UM.Theme.getSize("standard_list_lineheight").height
ButtonGroup.group: selectedScriptGroup
checkable: true
checked:
{
if (manager.selectedScriptIndex == index)
{
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
return true
}
else
{
return false
}
}
background: Rectangle
{
color: activeScriptButton.checked ? UM.Theme.getColor("background_3") : "transparent"
}
onClicked:
{
forceActiveFocus()
manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
}
RowLayout
{
anchors.fill: parent
UM.Label
{
Layout.fillWidth: true
Layout.preferredHeight: height
elide: Text.ElideRight
text: manager.getScriptLabelByKey(modelData.toString())
}
Item
{
id: downButton
Layout.preferredWidth: height
Layout.fillHeight: true
enabled: index != manager.scriptList.length - 1
MouseArea
{
anchors.fill: parent
onClicked:
{
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index + 1)
}
return manager.moveScript(index, index + 1)
}
}
UM.ColorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
color: parent.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("text_disabled")
source: UM.Theme.getIcon("ChevronSingleDown")
}
}
Item
{
id: upButton
Layout.preferredWidth: height
Layout.fillHeight: true
enabled: index != 0
MouseArea
{
anchors.fill: parent
onClicked:
{
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index - 1)
}
return manager.moveScript(index, index - 1)
}
}
UM.ColorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
color: upButton.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("text_disabled")
source: UM.Theme.getIcon("ChevronSingleUp")
}
}
Item
{
id: removeButton
Layout.preferredWidth: height
Layout.fillHeight: true
MouseArea
{
anchors.fill: parent
onClicked: manager.removeScriptByIndex(index)
}
UM.ColorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
color: UM.Theme.getColor("text")
source: UM.Theme.getIcon("Cancel")
}
}
}
}
}
Cura.SecondaryButton
{
id: addButton
text: catalog.i18nc("@action", "Add a script")
onClicked: scriptsMenu.open()
}
}
Cura.Menu
{
id: scriptsMenu
Models.Instantiator
{
model: manager.loadedScriptList
Cura.MenuItem
{
text: manager.getScriptLabelByKey(modelData.toString())
onTriggered: manager.addScriptToList(modelData.toString())
}
onObjectAdded: function(index, object) { scriptsMenu.insertItem(index, object)}
onObjectRemoved: function(index, object) { scriptsMenu.removeItem(object) }
}
}
Rectangle
{
color: UM.Theme.getColor("main_background")
anchors.left: activeScripts.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
height: parent.height
id: settingsPanel
UM.Label
{
id: scriptSpecsHeader
text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
anchors
{
top: parent.top
topMargin: base.textMargin
left: parent.left
leftMargin: base.textMargin
right: parent.right
rightMargin: base.textMargin
}
elide: Text.ElideRight
height: 20 * screenScaleFactor
font: UM.Theme.getFont("large_bold")
}
ListView
{
id: listview
anchors
{
top: scriptSpecsHeader.bottom
topMargin: settingsPanel.textMargin
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
bottom: parent.bottom
}
ScrollBar.vertical: UM.ScrollBar {}
clip: true
visible: manager.selectedScriptDefinitionId != ""
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
{
id: definitionsModel
containerId: manager.selectedScriptDefinitionId
onContainerIdChanged: definitionsModel.setAllVisible(true)
showAll: true
}
delegate: Loader
{
id: settingLoader
width: listview.width
height:
{
if (provider.properties.enabled == "True" && model.type != undefined)
{
if (definition && definition.comments && definition.comments.toLowerCase() === "multiline")
{
return UM.Theme.getSize("standard_list_lineheight").height + UM.Theme.getSize("narrow_margin").height + (UM.Theme.getSize("setting_control").height * 3);
}
return UM.Theme.getSize("section").height;
}
else
{
return 0
}
}
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider
property var globalPropertyProvider: inheritStackProvider
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded:
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
// Pass properties explicitly to custom components that don't extend SettingItem
if (settingLoader.item.hasOwnProperty("definition")) {
settingLoader.item.definition = settingLoader.definition
}
if (settingLoader.item.hasOwnProperty("settingDefinitionsModel")) {
settingLoader.item.settingDefinitionsModel = settingLoader.settingDefinitionsModel
}
if (settingLoader.item.hasOwnProperty("propertyProvider")) {
settingLoader.item.propertyProvider = settingLoader.propertyProvider
}
if (settingLoader.item.hasOwnProperty("globalPropertyProvider")) {
settingLoader.item.globalPropertyProvider = settingLoader.globalPropertyProvider
}
}
sourceComponent:
{
switch(model.type)
{
case "int":
return settingTextField
case "float":
return settingTextField
case "enum":
return settingComboBox
case "extruder":
return settingExtruder
case "bool":
return settingCheckBox
case "str":
// Check if comments field indicates multiline
if (definition && definition.comments && definition.comments.toLowerCase() === "multiline")
{
return settingTextArea;
}
return settingTextField
case "category":
return settingCategory
default:
return settingUnknown
}
}
UM.SettingPropertyProvider
{
id: provider
containerStackId: manager.selectedScriptStackId
key: model.key ? model.key : "None"
watchedProperties: [ "value", "enabled", "state", "validationState" ]
storeIndex: 0
}
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{
id: inheritStackProvider
containerStack: Cura.MachineManager.activeMachine
key: model.key ? model.key : "None"
watchedProperties: [ "limit_to_extruder" ]
}
Connections
{
target: item
function onShowTooltip(text)
{
tooltip.text = text;
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
tooltip.show(position);
tooltip.target.x = position.x + 1;
}
function onHideTooltip() { tooltip.hide() }
}
}
}
}
Cura.PrintSetupTooltip
{
id: tooltip
}
Component
{
id: settingTextField;
Cura.SettingTextField { }
}
Component
{
id: settingTextArea
// Custom multiline text area component for post-processing script settings
// Triggered when setting has "comments": "multiline" property
Item
{
id: base
// Properties passed from the Loader
property var definition
property var settingDefinitionsModel
property var propertyProvider
property var globalPropertyProvider
// Standard setting item properties (required by loader but unused in this component)
property bool showRevertButton: false
property bool showInheritButton: false
property bool showLinkedSettingIcon: false
property bool doDepthIndentation: false
property bool doQualityUserSettingEmphasis: false
// Internal state tracking (unused but kept for potential future use)
property string textBeforeEdit
property bool textHasChanged
// Signals for tooltip support (required by Connections in loader)
signal showTooltip(string text)
signal hideTooltip()
width: parent.width
// Height calculation: label height + spacing + text area (3x normal height for multiline editing)
height: UM.Theme.getSize("standard_list_lineheight").height + UM.Theme.getSize("narrow_margin").height + (UM.Theme.getSize("setting_control").height * 3)
UM.Label
{
id: labelText
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: UM.Theme.getSize("standard_list_lineheight").height
text: definition ? definition.label : ""
elide: Text.ElideRight
font: UM.Theme.getFont("default_bold")
color: UM.Theme.getColor("text")
}
Flickable
{
id: flickable
anchors.top: labelText.bottom
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
anchors.left: parent.left
anchors.right: parent.right
// Right margin to prevent overlap with parent ListView scrollbar
anchors.rightMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
height: UM.Theme.getSize("setting_control").height * 3
clip: true
ScrollBar.vertical: UM.ScrollBar { }
TextArea.flickable: TextArea
{
id: textArea
enabled: propertyProvider && propertyProvider.properties ? propertyProvider.properties.enabled === "True" : true
// Explicit undefined check to prevent QML warnings about undefined QString assignment
text: (propertyProvider && propertyProvider.properties && propertyProvider.properties.value !== undefined) ? propertyProvider.properties.value : ""
background: Rectangle
{
color: UM.Theme.getColor("setting_control")
border.color: textArea.activeFocus ? UM.Theme.getColor("text_field_border_active") : UM.Theme.getColor("text_field_border")
border.width: UM.Theme.getSize("default_lining").width
}
onTextChanged:
{
// Save value on each keystroke when focused (live update)
if (activeFocus && propertyProvider)
{
propertyProvider.setPropertyValue("value", text);
}
}
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
selectionColor: UM.Theme.getColor("text_selection")
selectedTextColor: UM.Theme.getColor("text")
wrapMode: TextEdit.NoWrap
selectByMouse: true
// Allow Enter/Return to insert newlines instead of closing dialog
Keys.onReturnPressed: function(event) { event.accepted = false; }
Keys.onEnterPressed: function(event) { event.accepted = false; }
// Escape key removes focus from text area
Keys.onEscapePressed: function(event) { focus = false; event.accepted = true; }
}
}
}
}
Component
{
id: settingComboBox;
Cura.SettingComboBox { }
}
Component
{
id: settingExtruder;
Cura.SettingExtruder { }
}
Component
{
id: settingCheckBox;
Cura.SettingCheckBox { }
}
Component
{
id: settingCategory;
Cura.SettingCategory { }
}
Component
{
id: settingUnknown;
Cura.SettingUnknown { }
}
}
rightButtons: Cura.TertiaryButton
{
text: catalog.i18nc("@action:button", "Close")
onClicked: dialog.accept()
}
Item
{
objectName: "postProcessingSaveAreaButton"
visible: activeScriptsList.count > 0
height: UM.Theme.getSize("action_button").height
width: height
Cura.SecondaryButton
{
height: UM.Theme.getSize("action_button").height
tooltip:
{
var tipText = catalog.i18nc("@info:tooltip", "Change active post-processing scripts.");
if (activeScriptsList.count > 0)
{
tipText += "<br><br>" + catalog.i18ncp("@info:tooltip",
"The following script is active:",
"The following scripts are active:",
activeScriptsList.count
) + "<ul>";
for(var i = 0; i < activeScriptsList.count; i++)
{
tipText += "<li>" + manager.getScriptLabelByKey(manager.scriptList[i]) + "</li>";
}
tipText += "</ul>";
}
return tipText
}
toolTipContentAlignment: UM.Enums.ContentAlignment.AlignLeft
onClicked: dialog.show()
iconSource: Qt.resolvedUrl("Script.svg")
fixedWidthMode: false
}
Cura.NotificationIcon
{
id: activeScriptCountIcon
visible: activeScriptsList.count > 0
anchors
{
horizontalCenter: parent.right
verticalCenter: parent.top
}
labelText: activeScriptsList.count
}
}
}