forked from qt-creator/qt-creator
Merge "Merge remote-tracking branch 'origin/qds/dev'"
This commit is contained in:
@@ -196,9 +196,7 @@ function(qtc_enable_sanitize _target _sanitize_flags)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(qtc_deeper_concept_diagnostic_depth _target)
|
function(qtc_deeper_concept_diagnostic_depth _target)
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
target_compile_options("${_target}" PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fconcepts-diagnostics-depth=8>)
|
||||||
target_compile_options("${_target}" PUBLIC -fconcepts-diagnostics-depth=8)
|
|
||||||
endif()
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(qtc_add_link_flags_no_undefined target)
|
function(qtc_add_link_flags_no_undefined target)
|
||||||
|
@@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS
|
|||||||
Texteditor
|
Texteditor
|
||||||
UpdateInfo
|
UpdateInfo
|
||||||
VcsBase
|
VcsBase
|
||||||
assetexporterplugin
|
|
||||||
componentsplugin
|
componentsplugin
|
||||||
qmlpreviewplugin
|
qmlpreviewplugin
|
||||||
qtquickplugin)
|
qtquickplugin)
|
||||||
|
@@ -141,6 +141,10 @@
|
|||||||
\externalpage https://doc.qt.io/qtdesignstudio/qtquick-positioning.html#using-layouts
|
\externalpage https://doc.qt.io/qtdesignstudio/qtquick-positioning.html#using-layouts
|
||||||
\title Using layouts
|
\title Using layouts
|
||||||
*/
|
*/
|
||||||
|
/*!
|
||||||
|
\externalpage https://www.qt.io/blog/qt-design-studio-4.7-released
|
||||||
|
\title Qt Design Studio 4.7 released
|
||||||
|
*/
|
||||||
/*!
|
/*!
|
||||||
\externalpage https://www.qt.io/blog/qt-design-studio-4.6-released
|
\externalpage https://www.qt.io/blog/qt-design-studio-4.6-released
|
||||||
\title Qt Design Studio 4.6 released
|
\title Qt Design Studio 4.6 released
|
||||||
|
@@ -14,8 +14,6 @@
|
|||||||
to \QDS and to edit them to create a UI.
|
to \QDS and to edit them to create a UI.
|
||||||
|
|
||||||
The following sections describe some of the main points of the webinar.
|
The following sections describe some of the main points of the webinar.
|
||||||
Select the \uicontrol Tutorials tab to watch the webinar video for the
|
|
||||||
full details.
|
|
||||||
|
|
||||||
\section1 Exporting from Adobe Photoshop
|
\section1 Exporting from Adobe Photoshop
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
\list
|
\list
|
||||||
\li In the \uicontrol 3D or \uicontrol Navigator view, right-click a 3D component and select
|
\li In the \uicontrol 3D or \uicontrol Navigator view, right-click a 3D component and select
|
||||||
\uicontrol {Export Component}.
|
\uicontrol {Export Bundle}.
|
||||||
\li In the \uicontrol {Material Browser} view, right-click a material and select
|
\li In the \uicontrol {Material Browser} view, right-click a material and select
|
||||||
\uicontrol {Export Material}.
|
\uicontrol {Export Material}.
|
||||||
\endlist
|
\endlist
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
To import a 3D component or material bundle, do one of the following:
|
To import a 3D component or material bundle, do one of the following:
|
||||||
\list
|
\list
|
||||||
\li In the \uicontrol {3D}, \uicontrol {2D}, or \uicontrol {Navigator} view, right-click
|
\li In the \uicontrol {3D}, \uicontrol {2D}, or \uicontrol {Navigator} view, right-click
|
||||||
and select \uicontrol {Import Component}. If you use this method to import a bundle of 3D
|
and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D
|
||||||
components, the 3D components are added to the 3D scene.
|
components, the 3D components are added to the 3D scene.
|
||||||
\li In \uicontrol {Content Library} > \uicontrol {User Assets}, right-click and select
|
\li In \uicontrol {Content Library} > \uicontrol {User Assets}, right-click and select
|
||||||
\uicontrol {Import Bundle}. If you use this method to import a bundle of 3D
|
\uicontrol {Import Bundle}. If you use this method to import a bundle of 3D
|
||||||
|
@@ -9,11 +9,12 @@
|
|||||||
|
|
||||||
Read the \QDS release blog posts to see what's new in every version.
|
Read the \QDS release blog posts to see what's new in every version.
|
||||||
|
|
||||||
\section1 \QDS Release blog posts
|
\section1 \QDS release blog posts
|
||||||
|
|
||||||
\section2 \QDS 4
|
\section2 \QDS 4
|
||||||
|
|
||||||
\list
|
\list
|
||||||
|
\li \l{Qt Design Studio 4.7 released}
|
||||||
\li \l{Qt Design Studio 4.6 released}
|
\li \l{Qt Design Studio 4.6 released}
|
||||||
\li \l{Qt Design Studio 4.5.1 released}
|
\li \l{Qt Design Studio 4.5.1 released}
|
||||||
\li \l{Qt Design Studio 4.5 released}
|
\li \l{Qt Design Studio 4.5 released}
|
||||||
|
@@ -141,6 +141,15 @@ StudioControls.Menu {
|
|||||||
onTriggered: root.rootView.editAssetComponent(root.__selectedAssetPathsList[0])
|
onTriggered: root.rootView.editAssetComponent(root.__selectedAssetPathsList[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuItem {
|
||||||
|
id: updateComponent
|
||||||
|
text: qsTr("Reimport 3D Asset")
|
||||||
|
visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1
|
||||||
|
&& root.rootView.assetIsImported3d(root.__selectedAssetPathsList[0])
|
||||||
|
height: editComponent.visible ? editComponent.implicitHeight : 0
|
||||||
|
onTriggered: root.rootView.updateAssetComponent(root.__selectedAssetPathsList[0])
|
||||||
|
}
|
||||||
|
|
||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
id: addTexturesItem
|
id: addTexturesItem
|
||||||
text: qsTr("Add Texture")
|
text: qsTr("Add Texture")
|
||||||
|
@@ -5,6 +5,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
import HelperWidgets as HelperWidgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
@@ -21,13 +22,13 @@ Column {
|
|||||||
Row {
|
Row {
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("From")
|
text: qsTr("From")
|
||||||
tooltip: qsTr("Sets the component and its property from which the value is copied.")
|
tooltip: qsTr("Sets the component and its property from which the value is copied.")
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("To")
|
text: qsTr("To")
|
||||||
tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.")
|
tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.")
|
||||||
@@ -49,7 +50,7 @@ Column {
|
|||||||
onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex
|
onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: backend.targetNode
|
text: backend.targetNode
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
@@ -49,8 +49,6 @@ StudioControls.PopupDialog {
|
|||||||
ConnectionsDialogForm {
|
ConnectionsDialogForm {
|
||||||
id: form
|
id: form
|
||||||
|
|
||||||
parentWindow: root.window
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.backend
|
target: root.backend
|
||||||
function onPopupShouldClose() {
|
function onPopupShouldClose() {
|
||||||
|
@@ -6,6 +6,8 @@ import QtQuick.Controls
|
|||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
import ScriptsEditor as ScriptsEditor
|
||||||
|
import ScriptEditorBackend
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
@@ -16,8 +18,7 @@ Column {
|
|||||||
|
|
||||||
property var backend
|
property var backend
|
||||||
|
|
||||||
property bool keepOpen: expressionDialogLoader.visible
|
property bool keepOpen: scriptEditor.keepOpen
|
||||||
property Window parentWindow: null
|
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: root.verticalSpacing
|
spacing: root.verticalSpacing
|
||||||
@@ -29,13 +30,13 @@ Column {
|
|||||||
Row {
|
Row {
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Signal")
|
text: qsTr("Signal")
|
||||||
tooltip: qsTr("Sets an interaction method that connects to the <b>Target</b> component.")
|
tooltip: qsTr("Sets an interaction method that connects to the <b>Target</b> component.")
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Action")
|
text: qsTr("Action")
|
||||||
tooltip: qsTr("Sets an action that is associated with the selected <b>Target</b> component's <b>Signal</b>.")
|
tooltip: qsTr("Sets an action that is associated with the selected <b>Target</b> component's <b>Signal</b>.")
|
||||||
@@ -58,259 +59,40 @@ Column {
|
|||||||
onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex
|
onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
ScriptsEditor.ActionsComboBox {
|
||||||
id: action
|
id: action
|
||||||
style: StudioTheme.Values.connectionPopupControlStyle
|
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
textRole: "text"
|
|
||||||
valueRole: "value"
|
|
||||||
///model.getData(currentIndex, "role")
|
|
||||||
property int indexFromBackend: indexOfValue(backend.actionType)
|
|
||||||
onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend
|
|
||||||
onActivated: backend.changeActionType(action.currentValue)
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.CallFunction
|
|
||||||
text: qsTr("Call Function")
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.Assign
|
|
||||||
text: qsTr("Assign")
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.ChangeState
|
|
||||||
text: qsTr("Change State")
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.SetProperty
|
|
||||||
text: qsTr("Set Property")
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.PrintMessage
|
|
||||||
text: qsTr("Print Message")
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
value: ConnectionModelStatementDelegate.Custom
|
|
||||||
text: qsTr("Custom")
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatementEditor {
|
|
||||||
width: root.width
|
|
||||||
actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom
|
|
||||||
horizontalSpacing: root.horizontalSpacing
|
|
||||||
columnWidth: root.columnWidth
|
|
||||||
statement: backend.okStatement
|
|
||||||
backend: root.backend
|
backend: root.backend
|
||||||
spacing: root.verticalSpacing
|
|
||||||
}
|
|
||||||
|
|
||||||
HelperWidgets.AbstractButton {
|
|
||||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
|
||||||
width: 160
|
|
||||||
buttonIcon: qsTr("Add Condition")
|
|
||||||
tooltip: qsTr("Sets a logical condition for the selected <b>Signal</b>. It works with the properties of the <b>Target</b> component.")
|
|
||||||
iconSize: StudioTheme.Values.baseFontSize
|
|
||||||
iconFontFamily: StudioTheme.Constants.font.family
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && !backend.hasCondition
|
|
||||||
|
|
||||||
onClicked: backend.addCondition()
|
|
||||||
}
|
|
||||||
|
|
||||||
HelperWidgets.AbstractButton {
|
|
||||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
|
||||||
width: 160
|
|
||||||
buttonIcon: qsTr("Remove Condition")
|
|
||||||
tooltip: qsTr("Removes the logical condition for the <b>Target</b> component.")
|
|
||||||
iconSize: StudioTheme.Values.baseFontSize
|
|
||||||
iconFontFamily: StudioTheme.Constants.font.family
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition
|
|
||||||
|
|
||||||
onClicked: backend.removeCondition()
|
|
||||||
}
|
|
||||||
|
|
||||||
ExpressionBuilder {
|
|
||||||
style: StudioTheme.Values.connectionPopupControlStyle
|
|
||||||
width: root.width
|
|
||||||
|
|
||||||
visible: backend.hasCondition
|
|
||||||
model: backend.conditionListModel
|
|
||||||
|
|
||||||
onRemove: function(index) {
|
|
||||||
//console.log("remove", index)
|
|
||||||
backend.conditionListModel.removeToken(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdate: function(index, value) {
|
|
||||||
//console.log("update", index, value)
|
|
||||||
backend.conditionListModel.updateToken(index, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
onAdd: function(value) {
|
|
||||||
//console.log("add", value)
|
|
||||||
backend.conditionListModel.appendToken(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
onInsert: function(index, value, type) {
|
|
||||||
//console.log("insert", index, value, type)
|
|
||||||
if (type === ConditionListModel.Intermediate)
|
|
||||||
backend.conditionListModel.insertIntermediateToken(index, value)
|
|
||||||
else if (type === ConditionListModel.Shadow)
|
|
||||||
backend.conditionListModel.insertShadowToken(index, value)
|
|
||||||
else
|
|
||||||
backend.conditionListModel.insertToken(index, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetValue: function(index, value) {
|
|
||||||
//console.log("setValue", index, value)
|
|
||||||
backend.conditionListModel.setShadowToken(index, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HelperWidgets.AbstractButton {
|
ScriptsEditor.ScriptEditorForm {
|
||||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
id: scriptEditor
|
||||||
width: 160
|
|
||||||
buttonIcon: qsTr("Add Else Statement")
|
|
||||||
tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.")
|
|
||||||
iconSize: StudioTheme.Values.baseFontSize
|
|
||||||
iconFontFamily: StudioTheme.Constants.font.family
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
|
||||||
&& backend.hasCondition && !backend.hasElse
|
|
||||||
|
|
||||||
onClicked: backend.addElse()
|
anchors.left: parent.left
|
||||||
}
|
anchors.right: parent.right
|
||||||
|
|
||||||
HelperWidgets.AbstractButton {
|
|
||||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
|
||||||
width: 160
|
|
||||||
buttonIcon: qsTr("Remove Else Statement")
|
|
||||||
tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.")
|
|
||||||
iconSize: StudioTheme.Values.baseFontSize
|
|
||||||
iconFontFamily: StudioTheme.Constants.font.family
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
|
||||||
&& backend.hasCondition && backend.hasElse
|
|
||||||
|
|
||||||
onClicked: backend.removeElse()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Else Statement
|
|
||||||
StatementEditor {
|
|
||||||
width: root.width
|
|
||||||
actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom
|
|
||||||
horizontalSpacing: root.horizontalSpacing
|
horizontalSpacing: root.horizontalSpacing
|
||||||
|
verticalSpacing: root.verticalSpacing
|
||||||
columnWidth: root.columnWidth
|
columnWidth: root.columnWidth
|
||||||
statement: backend.koStatement
|
spacing: root.spacing
|
||||||
|
|
||||||
backend: root.backend
|
backend: root.backend
|
||||||
spacing: root.verticalSpacing
|
|
||||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
|
||||||
&& backend.hasCondition && backend.hasElse
|
|
||||||
}
|
|
||||||
|
|
||||||
// code preview toolbar
|
currentAction: action.currentValue ?? StatementDelegate.Custom
|
||||||
Column {
|
|
||||||
id: miniToolbarEditor
|
|
||||||
width: parent.width
|
|
||||||
spacing: -2
|
|
||||||
|
|
||||||
Rectangle {
|
itemTooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||||
id: miniToolbar
|
methodTooltip: qsTr("Sets the item component's method that is affected by the <b>Target</b> component's <b>Signal</b>.")
|
||||||
width: parent.width
|
fromTooltip: qsTr("Sets the component and its property from which the value is copied when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||||
height: editorButton.height + 2
|
toTooltip: qsTr("Sets the component and its property to which the copied value is assigned when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||||
radius: 4
|
addConditionTooltip: qsTr("Sets a logical condition for the selected <b>Signal</b>. It works with the properties of the <b>Target</b> component.")
|
||||||
z: -1
|
removeConditionTooltip: qsTr("Removes the logical condition for the <b>Target</b> component.")
|
||||||
color: StudioTheme.Values.themeConnectionEditorMicroToolbar
|
stateGroupTooltip: qsTr("Sets a <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||||
|
stateTooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||||
Row {
|
propertyTooltip: qsTr("Sets the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||||
spacing: 2
|
valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||||
HelperWidgets.AbstractButton {
|
messageTooltip: qsTr("Sets a text that is printed when the <b>Signal</b> of the <b>Target</b> component initiates.")
|
||||||
id: editorButton
|
|
||||||
style: StudioTheme.Values.microToolbarButtonStyle
|
|
||||||
buttonIcon: StudioTheme.Constants.codeEditor_medium
|
|
||||||
tooltip: qsTr("Write the conditions for the components and the signals manually.")
|
|
||||||
onClicked: expressionDialogLoader.show()
|
|
||||||
}
|
|
||||||
HelperWidgets.AbstractButton {
|
|
||||||
id: jumpToCodeButton
|
|
||||||
style: StudioTheme.Values.microToolbarButtonStyle
|
|
||||||
buttonIcon: StudioTheme.Constants.jumpToCode_medium
|
|
||||||
tooltip: qsTr("Jump to the code.")
|
|
||||||
onClicked: backend.jumpToCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editor
|
|
||||||
Rectangle {
|
|
||||||
id: editor
|
|
||||||
width: parent.width
|
|
||||||
height: 150
|
|
||||||
color: StudioTheme.Values.themeConnectionCodeEditor
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: code
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
text: backend.indentedSource
|
|
||||||
color: StudioTheme.Values.themeTextColor
|
|
||||||
font.pixelSize: StudioTheme.Values.myFontSize
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: expressionDialogLoader
|
|
||||||
parent: editor
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: false
|
|
||||||
active: visible
|
|
||||||
|
|
||||||
function show() {
|
|
||||||
expressionDialogLoader.visible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: Item {
|
|
||||||
id: bindingEditorParent
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
bindingEditor.showWidget()
|
|
||||||
bindingEditor.text = backend.source
|
|
||||||
bindingEditor.showControls(false)
|
|
||||||
bindingEditor.setMultilne(true)
|
|
||||||
bindingEditor.updateWindowName()
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionEditor {
|
|
||||||
id: bindingEditor
|
|
||||||
|
|
||||||
onRejected: {
|
|
||||||
bindingEditor.hideWidget()
|
|
||||||
expressionDialogLoader.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: {
|
|
||||||
backend.setNewSource(bindingEditor.text)
|
|
||||||
bindingEditor.hideWidget()
|
|
||||||
expressionDialogLoader.visible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
import HelperWidgets as HelperWidgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
@@ -18,7 +19,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: root.verticalSpacing
|
spacing: root.verticalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
text: qsTr("Type")
|
text: qsTr("Type")
|
||||||
tooltip: qsTr("Sets the category of the <b>Local Custom Property</b>.")
|
tooltip: qsTr("Sets the category of the <b>Local Custom Property</b>.")
|
||||||
}
|
}
|
||||||
@@ -37,13 +38,13 @@ Column {
|
|||||||
Row {
|
Row {
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Name")
|
text: qsTr("Name")
|
||||||
tooltip: qsTr("Sets a name for the <b>Local Custom Property</b>.")
|
tooltip: qsTr("Sets a name for the <b>Local Custom Property</b>.")
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Value")
|
text: qsTr("Value")
|
||||||
tooltip: qsTr("Sets a valid <b>Local Custom Property</b> value.")
|
tooltip: qsTr("Sets a valid <b>Local Custom Property</b> value.")
|
||||||
|
@@ -109,29 +109,35 @@ HelperWidgets.ScrollView {
|
|||||||
id: infoText
|
id: infoText
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||||
qsTr("<b>Content Library</b> effects are not supported in Qt5 projects.")
|
qsTr("<b>Content Library</b> effects are not supported in Qt5 projects.")
|
||||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
|
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport) {
|
||||||
qsTr('To use <b>Content Library</b>, first <a href="#add_import" style="text-decoration:none;color:%1">
|
qsTr('To use <b>Content Library</b> effects, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||||
add the QtQuick3D module</a> in the <b>Components</b> view.')
|
component in the <b>Components</b> view, or click
|
||||||
.arg(StudioTheme.Values.themeInteraction)
|
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||||
else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport)
|
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||||
|
} else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) {
|
||||||
qsTr("To use <b>Content Library</b>, version 6.4 or later of the QtQuick3D module is required.")
|
qsTr("To use <b>Content Library</b>, version 6.4 or later of the QtQuick3D module is required.")
|
||||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
|
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) {
|
||||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||||
else if (!ContentLibraryBackend.effectsModel.bundleExists)
|
} else if (!ContentLibraryBackend.effectsModel.bundleExists) {
|
||||||
qsTr("No effects available.")
|
qsTr("No effects available.")
|
||||||
else if (!searchBox.isEmpty())
|
} else if (!searchBox.isEmpty()) {
|
||||||
qsTr("No match found.")
|
qsTr("No match found.")
|
||||||
else
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: StudioTheme.Values.themeTextColor
|
color: StudioTheme.Values.themeTextColor
|
||||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||||
topPadding: 10
|
topPadding: 10
|
||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
visible: ContentLibraryBackend.effectsModel.isEmpty
|
visible: ContentLibraryBackend.effectsModel.isEmpty
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: root.width
|
||||||
|
|
||||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||||
}
|
}
|
||||||
|
@@ -114,30 +114,34 @@ HelperWidgets.ScrollView {
|
|||||||
id: infoText
|
id: infoText
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||||
qsTr("<b>Content Library</b> materials are not supported in Qt5 projects.")
|
qsTr("<b>Content Library</b> materials are not supported in Qt5 projects.")
|
||||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
|
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport) {
|
||||||
qsTr('To use <b>Content Library</b>, first <a href="#add_import" style="text-decoration:none;color:%1">
|
qsTr('To use <b>Content Library</b> materials, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||||
add the QtQuick3D module</a> in the <b>Components</b> view.')
|
component in the <b>Components</b> view, or click
|
||||||
.arg(StudioTheme.Values.themeInteraction)
|
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||||
else if (!root.materialsModel.hasRequiredQuick3DImport)
|
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||||
|
} else if (!root.materialsModel.hasRequiredQuick3DImport) {
|
||||||
qsTr("To use <b>Content Library</b>, version 6.3 or later of the QtQuick3D module is required.")
|
qsTr("To use <b>Content Library</b>, version 6.3 or later of the QtQuick3D module is required.")
|
||||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
|
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) {
|
||||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||||
else if (!root.materialsModel.bundleExists)
|
} else if (!root.materialsModel.bundleExists) {
|
||||||
qsTr("No materials available. Make sure you have an internet connection.")
|
qsTr("No materials available. Make sure you have an internet connection.")
|
||||||
else if (!searchBox.isEmpty())
|
} else if (!searchBox.isEmpty()) {
|
||||||
qsTr("No match found.")
|
qsTr("No match found.")
|
||||||
else
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: StudioTheme.Values.themeTextColor
|
color: StudioTheme.Values.themeTextColor
|
||||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||||
topPadding: 10
|
topPadding: 10
|
||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: root.width - x
|
width: root.width - x
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
||||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||||
}
|
}
|
||||||
|
@@ -213,26 +213,33 @@ Item {
|
|||||||
text: {
|
text: {
|
||||||
let categoryName = (categoryTitle === "3D") ? categoryTitle + " assets"
|
let categoryName = (categoryTitle === "3D") ? categoryTitle + " assets"
|
||||||
: categoryTitle.toLowerCase()
|
: categoryTitle.toLowerCase()
|
||||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||||
qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
|
qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
|
||||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures")
|
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") {
|
||||||
qsTr(`To use %1, first <a href="#add_import" style="text-decoration:none;color:%2">
|
qsTr('To use %1, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||||
add the <b>QtQuick3D</b> module</a> in the <b>Components</b> view.`)
|
component in the <b>Components</b> view, or click
|
||||||
|
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%2\">
|
||||||
|
here</span></a>.')
|
||||||
.arg(categoryName)
|
.arg(categoryName)
|
||||||
.arg(StudioTheme.Values.themeInteraction)
|
.arg(StudioTheme.Values.themeInteraction)
|
||||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures")
|
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") {
|
||||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||||
else if (categoryEmpty)
|
} else if (categoryEmpty) {
|
||||||
qsTr("There are no "+ categoryName + " in the <b>User Assets</b>.")
|
qsTr("There are no "+ categoryName + " in the <b>User Assets</b>.")
|
||||||
else
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: StudioTheme.Values.themeTextColor
|
color: StudioTheme.Values.themeTextColor
|
||||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||||
topPadding: 10
|
topPadding: 10
|
||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
visible: infoText.text !== ""
|
visible: infoText.text !== ""
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: root.width
|
||||||
|
|
||||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates as T
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
property alias icon: icon
|
||||||
|
|
||||||
|
property bool hover: mouseArea.containsMouse//false
|
||||||
|
property bool pressed: false
|
||||||
|
property bool forceVisible: false
|
||||||
|
|
||||||
|
implicitWidth: control.style.actionIndicatorSize.width
|
||||||
|
implicitHeight: control.style.actionIndicatorSize.height
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
z: 10
|
||||||
|
|
||||||
|
T.Label {
|
||||||
|
id: icon
|
||||||
|
anchors.fill: parent
|
||||||
|
text: StudioTheme.Constants.actionIcon
|
||||||
|
color: control.style.icon.idle
|
||||||
|
font.family: StudioTheme.Constants.iconFont.family
|
||||||
|
font.pixelSize: control.style.baseIconFontSize
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "hover"
|
||||||
|
when: control.hover && !control.pressed && control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: icon
|
||||||
|
scale: 1.2
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "disable"
|
||||||
|
when: !control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: icon
|
||||||
|
color: control.style.icon.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
//onContainsMouseChanged: control.hover = mouseArea.containsMouse
|
||||||
|
onClicked: control.clicked()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,365 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates as T
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
import StudioQuickUtils
|
||||||
|
|
||||||
|
T.SpinBox {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
|
property real realFrom: 0.0
|
||||||
|
property real realTo: 99.0
|
||||||
|
property real realValue: 1.0
|
||||||
|
property real realStepSize: 1.0
|
||||||
|
|
||||||
|
property alias labelColor: spinBoxInput.color
|
||||||
|
|
||||||
|
property int decimals: 0
|
||||||
|
|
||||||
|
property real minStepSize: {
|
||||||
|
var tmpMinStepSize = Number((control.realStepSize * 0.1).toFixed(control.decimals))
|
||||||
|
return (tmpMinStepSize) ? tmpMinStepSize : control.realStepSize
|
||||||
|
}
|
||||||
|
property real maxStepSize: {
|
||||||
|
var tmpMaxStepSize = Number((control.realStepSize * 10.0).toFixed(control.decimals))
|
||||||
|
return (tmpMaxStepSize < control.realTo) ? tmpMaxStepSize : control.realStepSize
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool edit: spinBoxInput.activeFocus
|
||||||
|
// This property is used to indicate the global hover state
|
||||||
|
property bool hover: (spinBoxInput.hover || spinBoxIndicatorUp.hover || spinBoxIndicatorDown.hover)
|
||||||
|
&& control.enabled
|
||||||
|
|
||||||
|
property bool dirty: false // user modification flag
|
||||||
|
|
||||||
|
property bool spinBoxIndicatorVisible: true
|
||||||
|
property real __spinBoxIndicatorWidth: control.style.spinBoxIndicatorSize.width
|
||||||
|
property real __spinBoxIndicatorHeight: control.height / 2 - control.style.borderWidth
|
||||||
|
|
||||||
|
property alias compressedValueTimer: myTimer
|
||||||
|
|
||||||
|
property string preFocusText: ""
|
||||||
|
|
||||||
|
signal realValueModified
|
||||||
|
signal compressedRealValueModified
|
||||||
|
signal indicatorPressed
|
||||||
|
|
||||||
|
locale: Utils.locale
|
||||||
|
|
||||||
|
// Use custom wheel handling due to bugs
|
||||||
|
property bool __wheelEnabled: false
|
||||||
|
wheelEnabled: false
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
width: control.style.controlSize.width
|
||||||
|
height: control.style.controlSize.height
|
||||||
|
|
||||||
|
leftPadding: spinBoxIndicatorDown.x + spinBoxIndicatorDown.width
|
||||||
|
rightPadding: control.style.borderWidth
|
||||||
|
|
||||||
|
font.pixelSize: control.style.baseFontSize
|
||||||
|
editable: true
|
||||||
|
|
||||||
|
// Leave this in for now
|
||||||
|
from: -99
|
||||||
|
value: 0
|
||||||
|
to: 99
|
||||||
|
|
||||||
|
function checkAndClearFocus() {
|
||||||
|
if (!spinBoxIndicatorUp.activeFocus && !spinBoxIndicatorDown.activeFocus && !spinBoxInput.activeFocus)
|
||||||
|
control.focus = false
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleValidator {
|
||||||
|
id: doubleValidator
|
||||||
|
locale: control.locale
|
||||||
|
notation: DoubleValidator.StandardNotation
|
||||||
|
decimals: control.decimals
|
||||||
|
bottom: Math.min(control.realFrom, control.realTo)
|
||||||
|
top: Math.max(control.realFrom, control.realTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
IntValidator {
|
||||||
|
id: intValidator
|
||||||
|
locale: control.locale
|
||||||
|
bottom: Math.round(Math.min(control.realFrom, control.realTo))
|
||||||
|
top: Math.round(Math.max(control.realFrom, control.realTo))
|
||||||
|
}
|
||||||
|
|
||||||
|
validator: control.decimals === 0 ? intValidator : doubleValidator
|
||||||
|
|
||||||
|
up.indicator: SpinBoxIndicator {
|
||||||
|
id: spinBoxIndicatorUp
|
||||||
|
style: control.style
|
||||||
|
parentHover: control.hover
|
||||||
|
parentEdit: control.edit
|
||||||
|
iconFlip: -1
|
||||||
|
visible: control.spinBoxIndicatorVisible
|
||||||
|
onRealPressed: control.indicatorPressed()
|
||||||
|
onRealReleased: control.realIncrease()
|
||||||
|
onRealPressAndHold: control.realIncrease()
|
||||||
|
x: control.style.borderWidth
|
||||||
|
y: control.style.borderWidth
|
||||||
|
width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0
|
||||||
|
height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0
|
||||||
|
|
||||||
|
realEnabled: (control.realFrom < control.realTo) ? (control.realValue < control.realTo)
|
||||||
|
: (control.realValue > control.realTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
down.indicator: SpinBoxIndicator {
|
||||||
|
id: spinBoxIndicatorDown
|
||||||
|
style: control.style
|
||||||
|
parentHover: control.hover
|
||||||
|
parentEdit: control.edit
|
||||||
|
visible: control.spinBoxIndicatorVisible
|
||||||
|
onRealPressed: control.indicatorPressed()
|
||||||
|
onRealReleased: control.realDecrease()
|
||||||
|
onRealPressAndHold: control.realDecrease()
|
||||||
|
x: control.style.borderWidth
|
||||||
|
y: spinBoxIndicatorUp.y + spinBoxIndicatorUp.height
|
||||||
|
width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0
|
||||||
|
height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0
|
||||||
|
|
||||||
|
realEnabled: (control.realFrom < control.realTo) ? (control.realValue > control.realFrom)
|
||||||
|
: (control.realValue < control.realFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: SpinBoxInput {
|
||||||
|
id: spinBoxInput
|
||||||
|
style: control.style
|
||||||
|
validator: control.validator
|
||||||
|
font: control.font
|
||||||
|
readOnly: !control.editable
|
||||||
|
inputMethodHints: control.inputMethodHints
|
||||||
|
|
||||||
|
function handleEditingFinished() {
|
||||||
|
control.checkAndClearFocus()
|
||||||
|
|
||||||
|
// Keep the dirty state before calling setValueFromInput(),
|
||||||
|
// it will be set to false (cleared) internally
|
||||||
|
var valueModified = control.dirty
|
||||||
|
|
||||||
|
control.setValueFromInput()
|
||||||
|
myTimer.stop()
|
||||||
|
|
||||||
|
// Only trigger the signal, if the value was modified
|
||||||
|
if (valueModified)
|
||||||
|
control.compressedRealValueModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditingFinished: {
|
||||||
|
spinBoxInput.focus = false
|
||||||
|
spinBoxInput.handleEditingFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextEdited: control.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
id: spinBoxBackground
|
||||||
|
color: control.style.background.idle
|
||||||
|
border.color: control.style.border.idle
|
||||||
|
border.width: control.style.borderWidth
|
||||||
|
x: 0
|
||||||
|
width: control.width
|
||||||
|
height: control.height
|
||||||
|
}
|
||||||
|
|
||||||
|
textFromValue: function (value, locale) {
|
||||||
|
// -128 is the value of QLocale::FloatingPointShortest
|
||||||
|
return Number(control.realValue).toLocaleString(locale, 'f', -128)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueFromText: function (text, locale) {
|
||||||
|
control.setRealValue(Number.fromLocaleString(locale, spinBoxInput.text))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "default"
|
||||||
|
when: control.enabled && !control.hover && !control.hovered
|
||||||
|
&& !control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
__wheelEnabled: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxInput
|
||||||
|
selectByMouse: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxBackground
|
||||||
|
color: control.style.background.idle
|
||||||
|
border.color: control.style.border.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hover"
|
||||||
|
when: control.enabled && control.hover && control.hovered
|
||||||
|
&& !control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxBackground
|
||||||
|
border.color: control.style.border.hover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "edit"
|
||||||
|
when: control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
__wheelEnabled: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxInput
|
||||||
|
selectByMouse: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxBackground
|
||||||
|
border.color: control.style.border.interaction
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "disable"
|
||||||
|
when: !control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxBackground
|
||||||
|
border.color: control.style.border.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: myTimer
|
||||||
|
repeat: false
|
||||||
|
running: false
|
||||||
|
interval: 400
|
||||||
|
onTriggered: control.compressedRealValueModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
onRealValueChanged: {
|
||||||
|
control.setRealValue(control.realValue) // sanitize and clamp realValue
|
||||||
|
spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||||
|
control.value = 0 // Without setting value back to 0, it can happen that one of
|
||||||
|
// the indicator will be disabled due to range logic.
|
||||||
|
}
|
||||||
|
onRealValueModified: myTimer.restart()
|
||||||
|
onFocusChanged: {
|
||||||
|
if (control.focus)
|
||||||
|
control.dirty = false
|
||||||
|
}
|
||||||
|
onDisplayTextChanged: spinBoxInput.text = control.displayText
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (control.activeFocus) { // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason)
|
||||||
|
control.preFocusText = spinBoxInput.text
|
||||||
|
spinBoxInput.selectAll()
|
||||||
|
} else {
|
||||||
|
// Make sure displayed value is correct after focus loss, as onEditingFinished
|
||||||
|
// doesn't trigger when value is something validator doesn't accept.
|
||||||
|
if (spinBoxInput.text === "")
|
||||||
|
spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||||
|
|
||||||
|
if (control.dirty)
|
||||||
|
spinBoxInput.handleEditingFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDecimalsChanged: spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||||
|
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
|
||||||
|
event.accepted = true
|
||||||
|
|
||||||
|
// Store current step size
|
||||||
|
var currStepSize = control.realStepSize
|
||||||
|
|
||||||
|
// Set realStepSize according to used modifier key
|
||||||
|
if (event.modifiers & Qt.ControlModifier)
|
||||||
|
control.realStepSize = control.minStepSize
|
||||||
|
|
||||||
|
if (event.modifiers & Qt.ShiftModifier)
|
||||||
|
control.realStepSize = control.maxStepSize
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Up)
|
||||||
|
control.realIncrease()
|
||||||
|
else
|
||||||
|
control.realDecrease()
|
||||||
|
|
||||||
|
// Reset realStepSize
|
||||||
|
control.realStepSize = currStepSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
spinBoxInput.text = control.preFocusText
|
||||||
|
control.dirty = true
|
||||||
|
spinBoxInput.handleEditingFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(v, lo, hi) {
|
||||||
|
return (v < lo || v > hi) ? Math.min(Math.max(lo, v), hi) : v
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValueFromInput() {
|
||||||
|
if (!control.dirty)
|
||||||
|
return
|
||||||
|
|
||||||
|
// FIX: This is a temporary fix for QTBUG-74239
|
||||||
|
var currValue = control.realValue
|
||||||
|
|
||||||
|
// Call the function but don't use return value. The realValue property
|
||||||
|
// will be implicitly set inside the function/procedure.
|
||||||
|
control.valueFromText(spinBoxInput.text, control.locale)
|
||||||
|
|
||||||
|
if (control.realValue !== currValue) {
|
||||||
|
control.realValueModified()
|
||||||
|
} else {
|
||||||
|
// Check if input text differs in format from the current value
|
||||||
|
var tmpInputValue = control.textFromValue(control.realValue, control.locale)
|
||||||
|
|
||||||
|
if (tmpInputValue !== spinBoxInput.text)
|
||||||
|
spinBoxInput.text = tmpInputValue
|
||||||
|
}
|
||||||
|
|
||||||
|
control.dirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRealValue(value) {
|
||||||
|
if (Number.isNaN(value))
|
||||||
|
value = 0
|
||||||
|
|
||||||
|
if (control.decimals === 0)
|
||||||
|
value = Math.round(value)
|
||||||
|
|
||||||
|
control.realValue = control.clamp(value,
|
||||||
|
control.validator.bottom,
|
||||||
|
control.validator.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
function realDecrease() {
|
||||||
|
// Store the current value for comparison
|
||||||
|
var currValue = control.realValue
|
||||||
|
control.valueFromText(spinBoxInput.text, control.locale)
|
||||||
|
|
||||||
|
control.setRealValue(control.realValue - control.realStepSize)
|
||||||
|
|
||||||
|
if (control.realValue !== currValue)
|
||||||
|
control.realValueModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
function realIncrease() {
|
||||||
|
// Store the current value for comparison
|
||||||
|
var currValue = control.realValue
|
||||||
|
control.valueFromText(spinBoxInput.text, control.locale)
|
||||||
|
|
||||||
|
control.setRealValue(control.realValue + control.realStepSize)
|
||||||
|
|
||||||
|
if (control.realValue !== currValue)
|
||||||
|
control.realValueModified()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,247 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates as T
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
|
property bool hover: spinBoxIndicatorMouseArea.containsMouse
|
||||||
|
property bool pressed: spinBoxIndicatorMouseArea.containsPress
|
||||||
|
property bool released: false
|
||||||
|
property bool realEnabled: true
|
||||||
|
|
||||||
|
property bool parentHover: false
|
||||||
|
property bool parentEdit: false
|
||||||
|
|
||||||
|
signal realPressed
|
||||||
|
signal realPressAndHold
|
||||||
|
signal realReleased
|
||||||
|
|
||||||
|
property alias iconFlip: spinBoxIndicatorIconScale.yScale
|
||||||
|
|
||||||
|
|
||||||
|
color: control.style.background.idle
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
onEnabledChanged: control.syncEnabled()
|
||||||
|
onRealEnabledChanged: {
|
||||||
|
control.syncEnabled()
|
||||||
|
if (control.realEnabled === false) {
|
||||||
|
pressAndHoldTimer.stop()
|
||||||
|
spinBoxIndicatorMouseArea.pressedAndHeld = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is meant to synchronize enabled with realEnabled to avoid
|
||||||
|
// the internal logic messing with the actual state.
|
||||||
|
function syncEnabled() {
|
||||||
|
control.enabled = control.realEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: pressAndHoldTimer
|
||||||
|
repeat: true
|
||||||
|
running: false
|
||||||
|
interval: 100
|
||||||
|
onTriggered: control.realPressAndHold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This MouseArea is a workaround to avoid some hover state related bugs
|
||||||
|
// when using the actual signal 'up.hovered'. QTBUG-74688
|
||||||
|
MouseArea {
|
||||||
|
id: spinBoxIndicatorMouseArea
|
||||||
|
|
||||||
|
property bool pressedAndHeld: false
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
pressAndHoldInterval: 500
|
||||||
|
onPressed: function(mouse) {
|
||||||
|
//if (control.__parentControl.activeFocus)
|
||||||
|
// control.forceActiveFocus()
|
||||||
|
|
||||||
|
control.realPressed()
|
||||||
|
mouse.accepted = true
|
||||||
|
}
|
||||||
|
onPressAndHold: {
|
||||||
|
pressAndHoldTimer.restart()
|
||||||
|
spinBoxIndicatorMouseArea.pressedAndHeld = true
|
||||||
|
}
|
||||||
|
onReleased: function(mouse) {
|
||||||
|
// Only trigger real released when pressAndHold isn't active
|
||||||
|
if (!pressAndHoldTimer.running && containsMouse)
|
||||||
|
control.realReleased()
|
||||||
|
pressAndHoldTimer.stop()
|
||||||
|
mouse.accepted = true
|
||||||
|
spinBoxIndicatorMouseArea.pressedAndHeld = false
|
||||||
|
}
|
||||||
|
onEntered: {
|
||||||
|
if (spinBoxIndicatorMouseArea.pressedAndHeld)
|
||||||
|
pressAndHoldTimer.restart()
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
if (pressAndHoldTimer.running)
|
||||||
|
pressAndHoldTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T.Label {
|
||||||
|
id: spinBoxIndicatorIcon
|
||||||
|
text: StudioTheme.Constants.upDownSquare2
|
||||||
|
color: control.style.icon.idle
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: control.style.smallIconFontSize
|
||||||
|
font.family: StudioTheme.Constants.iconFont.family
|
||||||
|
anchors.fill: parent
|
||||||
|
transform: Scale {
|
||||||
|
id: spinBoxIndicatorIconScale
|
||||||
|
origin.x: 0
|
||||||
|
origin.y: spinBoxIndicatorIcon.height / 2
|
||||||
|
yScale: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "default"
|
||||||
|
when: control.enabled && !control.hover && !control.parentEdit && !control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "globalHover"
|
||||||
|
when: control.enabled && !control.hover && !control.parentEdit && control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hover"
|
||||||
|
when: control.enabled && control.hover && !control.pressed && control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.hover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "press"
|
||||||
|
when: control.enabled && control.pressed
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "parentEdit"
|
||||||
|
when: control.parentEdit && control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "disable"
|
||||||
|
when: !control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
color: control.style.icon.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "default"
|
||||||
|
when: !control.parentEdit && !control.hover && !control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "globalHover"
|
||||||
|
when: control.enabled && !control.hover && !control.parentEdit && control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.globalHover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hover"
|
||||||
|
when: control.enabled && control.hover && !control.pressed && control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.hover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "press"
|
||||||
|
when: control.enabled && control.pressed
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.interaction
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "parentEdit"
|
||||||
|
when: control.parentEdit && control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "disable"
|
||||||
|
when: !control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.disabled
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "limit"
|
||||||
|
when: !control.enabled && !control.realEnabled && control.parentHover
|
||||||
|
PropertyChanges {
|
||||||
|
target: spinBoxIndicatorIcon
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.background.idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates as T
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
|
property bool edit: control.activeFocus
|
||||||
|
property bool drag: false
|
||||||
|
property bool hover: hoverHandler.hovered && control.enabled
|
||||||
|
|
||||||
|
z: 2
|
||||||
|
color: control.style.text.idle
|
||||||
|
selectionColor: control.style.text.selection
|
||||||
|
selectedTextColor: control.style.text.selectedText
|
||||||
|
|
||||||
|
horizontalAlignment: Qt.AlignLeft
|
||||||
|
verticalAlignment: Qt.AlignVCenter
|
||||||
|
leftPadding: control.style.inputHorizontalPadding
|
||||||
|
rightPadding: control.style.inputHorizontalPadding
|
||||||
|
|
||||||
|
selectByMouse: false
|
||||||
|
activeFocusOnPress: false
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
// TextInput focus needs to be set to activeFocus whenever it changes,
|
||||||
|
// otherwise TextInput will get activeFocus whenever the parent SpinBox gets
|
||||||
|
// activeFocus. This will lead to weird side effects.
|
||||||
|
onActiveFocusChanged: control.focus = control.activeFocus
|
||||||
|
|
||||||
|
HoverHandler { id: hoverHandler }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: textInputBackground
|
||||||
|
x: 0
|
||||||
|
y: control.style.borderWidth
|
||||||
|
z: -1
|
||||||
|
width: control.width
|
||||||
|
height: control.height - (control.style.borderWidth * 2)
|
||||||
|
color: control.style.background.idle
|
||||||
|
border.width: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we get Up and Down key press events first
|
||||||
|
Keys.onShortcutOverride: function(event) {
|
||||||
|
event.accepted = (event.key === Qt.Key_Up || event.key === Qt.Key_Down)
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "default"
|
||||||
|
when: control.enabled && !control.edit && !control.hover
|
||||||
|
PropertyChanges {
|
||||||
|
target: textInputBackground
|
||||||
|
color: control.style.background.idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "globalHover"
|
||||||
|
when: !control.hover && !control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: textInputBackground
|
||||||
|
color: control.style.background.globalHover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hover"
|
||||||
|
when: control.hover && !control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: textInputBackground
|
||||||
|
color: control.style.background.hover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "edit"
|
||||||
|
when: control.edit
|
||||||
|
PropertyChanges {
|
||||||
|
target: textInputBackground
|
||||||
|
color: control.style.background.interaction
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "disable"
|
||||||
|
when: !control.enabled
|
||||||
|
PropertyChanges {
|
||||||
|
target: textInputBackground
|
||||||
|
color: control.style.background.disabled
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: control
|
||||||
|
color: control.style.text.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Templates as T
|
import QtQuick.Templates as T
|
||||||
|
|
||||||
import StudioTheme 1.0 as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
T.Switch {
|
T.Switch {
|
||||||
id: control
|
id: control
|
||||||
@@ -14,6 +14,7 @@ T.Switch {
|
|||||||
// This property is used to indicate the global hover state
|
// This property is used to indicate the global hover state
|
||||||
property bool hover: control.hovered && control.enabled
|
property bool hover: control.hovered && control.enabled
|
||||||
property bool edit: false
|
property bool edit: false
|
||||||
|
property bool readonly: false
|
||||||
|
|
||||||
property alias labelVisible: label.visible
|
property alias labelVisible: label.visible
|
||||||
property alias labelColor: label.color
|
property alias labelColor: label.color
|
||||||
@@ -33,6 +34,8 @@ T.Switch {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
activeFocusOnTab: false
|
activeFocusOnTab: false
|
||||||
|
|
||||||
|
enabled: !control.readonly
|
||||||
|
|
||||||
indicator: Rectangle {
|
indicator: Rectangle {
|
||||||
id: switchBackground
|
id: switchBackground
|
||||||
x: 0
|
x: 0
|
||||||
@@ -60,6 +63,7 @@ T.Switch {
|
|||||||
color: control.style.icon.idle
|
color: control.style.icon.idle
|
||||||
border.width: 0
|
border.width: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: T.Label {
|
contentItem: T.Label {
|
||||||
@@ -133,7 +137,7 @@ T.Switch {
|
|||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "disable"
|
name: "disable"
|
||||||
when: !control.enabled && !control.checked
|
when: !control.enabled && !control.checked && !control.readonly
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: switchBackground
|
target: switchBackground
|
||||||
color: control.style.background.disabled
|
color: control.style.background.disabled
|
||||||
@@ -186,8 +190,19 @@ T.Switch {
|
|||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "disableChecked"
|
name: "disableChecked"
|
||||||
when: !control.enabled && control.checked
|
when: !control.enabled && control.checked && !control.readonly
|
||||||
extend: "disable"
|
extend: "disable"
|
||||||
|
},
|
||||||
|
|
||||||
|
State {
|
||||||
|
name: "readonly"
|
||||||
|
when: !control.enabled && !control.checked && control.readonly
|
||||||
|
extend: "default"
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "readonlyChecked"
|
||||||
|
when: !control.enabled && control.checked && control.readonly
|
||||||
|
extend: "defaultChecked"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,7 @@
|
|||||||
|
BindingIndicator 1.0 BindingIndicator.qml
|
||||||
MenuItem 1.0 MenuItem.qml
|
MenuItem 1.0 MenuItem.qml
|
||||||
|
SpinBox 1.0 SpinBox.qml
|
||||||
|
SpinBoxIndicator 1.0 SpinBoxIndicator.qml
|
||||||
|
SpinBoxInput 1.0 SpinBoxInput.qml
|
||||||
Switch 1.0 Switch.qml
|
Switch 1.0 Switch.qml
|
||||||
TextField 1.0 TextField.qml
|
TextField 1.0 TextField.qml
|
||||||
|
@@ -596,21 +596,27 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
if (!materialBrowserModel.isQt6Project)
|
if (!materialBrowserModel.isQt6Project) {
|
||||||
qsTr("<b>Material Browser</b> is not supported in Qt5 projects.")
|
qsTr("<b>Material Browser</b> is not supported in Qt5 projects.")
|
||||||
else if (!materialBrowserModel.hasQuick3DImport)
|
} else if (!materialBrowserModel.hasQuick3DImport) {
|
||||||
qsTr("To use <b>Material Browser</b>, first add the QtQuick3D module in the <b>Components</b> view.")
|
qsTr('To use the <b>Material Browser</b>, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||||
else if (!materialBrowserModel.hasMaterialLibrary)
|
component in the <b>Components</b> view, or click
|
||||||
|
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||||
|
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||||
|
} else if (!materialBrowserModel.hasMaterialLibrary) {
|
||||||
qsTr("<b>Material Browser</b> is disabled inside a non-visual component.")
|
qsTr("<b>Material Browser</b> is disabled inside a non-visual component.")
|
||||||
else
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: StudioTheme.Values.themeTextColor
|
color: StudioTheme.Values.themeTextColor
|
||||||
font.pixelSize: StudioTheme.Values.mediumFontSize
|
font.pixelSize: StudioTheme.Values.mediumFontSize
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
onLinkActivated: rootView.addQtQuick3D()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -186,6 +186,8 @@ Section {
|
|||||||
spacing: 1
|
spacing: 1
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||||
|
|
||||||
sectionHeight: 37
|
sectionHeight: 37
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -232,6 +234,8 @@ Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||||
|
|
||||||
sectionHeight: 37
|
sectionHeight: 37
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -305,6 +309,8 @@ Section {
|
|||||||
Section {
|
Section {
|
||||||
id: delegate
|
id: delegate
|
||||||
|
|
||||||
|
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||||
|
|
||||||
property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData)
|
property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData)
|
||||||
property bool wasExpanded: false
|
property bool wasExpanded: false
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ PropertyEditorPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DynamicPropertiesSection {
|
DynamicPropertiesSection {
|
||||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||||
visible: !hasMultiSelection
|
visible: !hasMultiSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +90,17 @@ PropertyEditorPane {
|
|||||||
|
|
||||||
StudioControls.TabButton {
|
StudioControls.TabButton {
|
||||||
text: backendValues.__classNamePrivateInternal.value
|
text: backendValues.__classNamePrivateInternal.value
|
||||||
|
onClicked: () => {
|
||||||
|
if (itemPane.searchBar.hasDoneSearch)
|
||||||
|
itemPane.searchBar.search();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StudioControls.TabButton {
|
StudioControls.TabButton {
|
||||||
text: qsTr("Layout")
|
text: qsTr("Layout")
|
||||||
|
onClicked: () => {
|
||||||
|
if (itemPane.searchBar.hasDoneSearch)
|
||||||
|
itemPane.searchBar.search();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ PropertyEditorPane {
|
|||||||
ComponentSection {}
|
ComponentSection {}
|
||||||
|
|
||||||
DynamicPropertiesSection {
|
DynamicPropertiesSection {
|
||||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||||
visible: !hasMultiSelection
|
visible: !hasMultiSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,4 +12,9 @@ Column {
|
|||||||
AnimationSection {
|
AnimationSection {
|
||||||
showDuration: false
|
showDuration: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScriptSection {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import HelperWidgets
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
import ScriptEditorBackend
|
||||||
|
import ScriptsEditor as ScriptsEditor
|
||||||
|
|
||||||
|
Section {
|
||||||
|
id: root
|
||||||
|
caption: qsTr("Script")
|
||||||
|
|
||||||
|
SectionLayout {
|
||||||
|
PropertyLabel {
|
||||||
|
text: ""
|
||||||
|
tooltip: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
SecondColumnLayout {
|
||||||
|
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||||
|
|
||||||
|
AbstractButton {
|
||||||
|
id: editScriptButton
|
||||||
|
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
width: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
buttonIcon: qsTr("Edit script")
|
||||||
|
iconFontFamily: StudioTheme.Constants.font.family
|
||||||
|
onClicked: dialog.show(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpandingSpacer {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data: [
|
||||||
|
StudioControls.PopupDialog {
|
||||||
|
id: dialog
|
||||||
|
|
||||||
|
readonly property real horizontalSpacing: 10
|
||||||
|
readonly property real verticalSpacing: 12
|
||||||
|
readonly property real columnWidth: (dialog.width - dialog.horizontalSpacing - (2 * dialog.anchorGap)) / 2
|
||||||
|
|
||||||
|
property var backend: ScriptEditorBackend
|
||||||
|
|
||||||
|
titleBar: Row {
|
||||||
|
id: titleBarRow
|
||||||
|
spacing: 30
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Text {
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
text: qsTr("Action")
|
||||||
|
font.pixelSize: StudioTheme.Values.myFontSize
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
ToolTipArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
tooltip: qsTr("Sets an action that is associated with the selected <b>ScriptAction</b>.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptsEditor.ActionsComboBox {
|
||||||
|
id: action
|
||||||
|
style: StudioTheme.Values.connectionPopupControlStyle
|
||||||
|
width: 180
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
backend: dialog.backend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptsEditor.ScriptEditorForm {
|
||||||
|
id: scriptEditor
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
horizontalSpacing: dialog.horizontalSpacing
|
||||||
|
verticalSpacing: dialog.verticalSpacing
|
||||||
|
columnWidth: dialog.columnWidth
|
||||||
|
spacing: dialog.verticalSpacing
|
||||||
|
|
||||||
|
backend: dialog.backend
|
||||||
|
|
||||||
|
currentAction: action.currentValue ?? StatementDelegate.Custom
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: modelNodeBackend
|
||||||
|
function onSelectionChanged() {
|
||||||
|
dialog.backend.update()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
onTapped: scriptEditor.forceActiveFocus()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -13,6 +13,11 @@ Rectangle {
|
|||||||
height: 400
|
height: 400
|
||||||
color: StudioTheme.Values.themePanelBackground
|
color: StudioTheme.Values.themePanelBackground
|
||||||
|
|
||||||
|
// Called from C++ to clear the search when the selected node changes
|
||||||
|
function clearSearch() {
|
||||||
|
// The function is empty, because it is a placeholder to match other panes
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
// Copyright (C) 2022 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import HelperWidgets 2.0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
// CubeMapTexture inherits Texture but doesn't provide any extra properties itself
|
|
||||||
TextureSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,8 +11,4 @@ Column {
|
|||||||
CustomMaterialSection {
|
CustomMaterialSection {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,4 @@ Column {
|
|||||||
DefaultMaterialSection {
|
DefaultMaterialSection {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
|
||||||
|
StudioControls.PopupDialog {
|
||||||
|
id: colorPopup
|
||||||
|
|
||||||
|
property QtObject loaderItem: loader.item
|
||||||
|
property color originalColor
|
||||||
|
required property color currentColor
|
||||||
|
|
||||||
|
signal activateColor(color : color)
|
||||||
|
|
||||||
|
width: 260
|
||||||
|
|
||||||
|
onOriginalColorChanged: loader.updateOriginalColor()
|
||||||
|
onClosing: loader.active = false
|
||||||
|
|
||||||
|
function open(showItem) {
|
||||||
|
loader.ensureActive()
|
||||||
|
colorPopup.show(showItem)
|
||||||
|
|
||||||
|
loader.updateOriginalColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
function ensureActive() {
|
||||||
|
if (!loader.active)
|
||||||
|
loader.active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOriginalColor() {
|
||||||
|
if (loader.status === Loader.Ready)
|
||||||
|
loader.item.originalColor = colorPopup.originalColor
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: StudioControls.ColorEditorPopup {
|
||||||
|
width: colorPopup.contentWidth
|
||||||
|
visible: colorPopup.visible
|
||||||
|
|
||||||
|
onActivateColor: (color) => {
|
||||||
|
colorPopup.activateColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: loader.item
|
||||||
|
property: "color"
|
||||||
|
value: colorPopup.currentColor
|
||||||
|
when: loader.status === Loader.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
loader.updateOriginalColor()
|
||||||
|
colorPopup.titleBar = loader.item.titleBarContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,266 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import HelperWidgets 2.0 as HelperWidgets
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||||
|
property alias pinned: pinButton.checked
|
||||||
|
property alias showPinButton: pinButton.visible
|
||||||
|
|
||||||
|
property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle {
|
||||||
|
// This is how you can override stuff from the control styles
|
||||||
|
baseIconFontSize: StudioTheme.Values.bigIconFontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: HelperWidgets.Controller
|
||||||
|
|
||||||
|
function onCloseContextMenu() {
|
||||||
|
root.closeContextMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: image.height
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
color: "#000000"
|
||||||
|
|
||||||
|
// Called from C++ to close context menu on focus out
|
||||||
|
function closeContextMenu()
|
||||||
|
{
|
||||||
|
modelMenu.close()
|
||||||
|
envMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPreview()
|
||||||
|
{
|
||||||
|
image.source = ""
|
||||||
|
image.source = "image://nodeInstance/preview"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
|
||||||
|
function onPreviewEnvChanged() {
|
||||||
|
envMenu.updateEnvParams(backend.previewEnv)
|
||||||
|
root.refreshPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPreviewModelChanged() {
|
||||||
|
root.refreshPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: image
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
source: "image://nodeInstance/preview"
|
||||||
|
cache: false
|
||||||
|
smooth: true
|
||||||
|
|
||||||
|
sourceSize.width: image.width
|
||||||
|
sourceSize.height: image.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: toolbarRect
|
||||||
|
|
||||||
|
radius: 10
|
||||||
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
|
width: optionsToolbar.width + 2 * toolbarRect.radius
|
||||||
|
height: optionsToolbar.height + toolbarRect.radius
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: -toolbarRect.radius
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: optionsToolbar
|
||||||
|
|
||||||
|
spacing: 5
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.horizontalCenterOffset: optionsToolbar.spacing
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: pinButton
|
||||||
|
|
||||||
|
style: root.buttonStyle
|
||||||
|
buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin
|
||||||
|
checkable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: previewEnvMenuButton
|
||||||
|
|
||||||
|
style: root.buttonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.textures_medium
|
||||||
|
tooltip: qsTr("Select preview environment.")
|
||||||
|
onClicked: envMenu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: previewModelMenuButton
|
||||||
|
|
||||||
|
style: root.buttonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.cube_medium
|
||||||
|
tooltip: qsTr("Select preview model.")
|
||||||
|
onClicked: modelMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.Menu {
|
||||||
|
id: modelMenu
|
||||||
|
|
||||||
|
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: modelMenuModel
|
||||||
|
ListElement {
|
||||||
|
modelName: qsTr("Cone")
|
||||||
|
modelStr: "#Cone"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelName: qsTr("Cube")
|
||||||
|
modelStr: "#Cube"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelName: qsTr("Cylinder")
|
||||||
|
modelStr: "#Cylinder"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelName: qsTr("Sphere")
|
||||||
|
modelStr: "#Sphere"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: modelMenuModel
|
||||||
|
StudioControls.MenuItemWithIcon {
|
||||||
|
text: modelName
|
||||||
|
onClicked: root.backend.previewModel = modelStr
|
||||||
|
checkable: true
|
||||||
|
checked: root.backend.previewModel === modelStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.Menu {
|
||||||
|
id: envMenu
|
||||||
|
|
||||||
|
property string previewEnvName
|
||||||
|
property string previewEnvValue
|
||||||
|
|
||||||
|
signal envParametersChanged()
|
||||||
|
|
||||||
|
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
|
||||||
|
|
||||||
|
Component.onCompleted: envMenu.updateEnvParams(root.backend.previewEnv)
|
||||||
|
|
||||||
|
function updateEnvParams(str: string) {
|
||||||
|
let eqFound = str.lastIndexOf("=")
|
||||||
|
let newEnvName = (eqFound > 0) ? str.substr(0, eqFound) : str
|
||||||
|
let newEnvValue = (eqFound > 0) ? str.substr(eqFound + 1, str.length - eqFound) : ""
|
||||||
|
|
||||||
|
if (envMenu.previewEnvName !== newEnvName
|
||||||
|
|| envMenu.previewEnvValue !== newEnvValue) {
|
||||||
|
envMenu.previewEnvName = newEnvName
|
||||||
|
envMenu.previewEnvValue = newEnvValue
|
||||||
|
envMenu.envParametersChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvMenuItem {
|
||||||
|
envName: qsTr("Basic")
|
||||||
|
envStr: "Basic"
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvMenuItem {
|
||||||
|
id: colorItem
|
||||||
|
|
||||||
|
property color color
|
||||||
|
property bool colorIsValid: false
|
||||||
|
|
||||||
|
envName: qsTr("Color")
|
||||||
|
envStr: "Color"
|
||||||
|
checked: false
|
||||||
|
|
||||||
|
Component.onCompleted: update()
|
||||||
|
onColorIsValidChanged: updatePopupOriginalColor()
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
colorItem.updatePopupOriginalColor()
|
||||||
|
colorPopup.open(colorItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
onColorChanged: {
|
||||||
|
colorItem.envStr = colorItem.checked
|
||||||
|
? "Color=" + color.toString()
|
||||||
|
: "Color"
|
||||||
|
colorItem.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePopupOriginalColor() {
|
||||||
|
if (colorItem.colorIsValid)
|
||||||
|
colorPopup.originalColor = colorItem.color
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
colorItem.checked = envMenu.previewEnvName === "Color"
|
||||||
|
if (colorItem.checked && envMenu.previewEnvValue) {
|
||||||
|
colorItem.color = envMenu.previewEnvValue
|
||||||
|
colorItem.colorIsValid = true
|
||||||
|
} else {
|
||||||
|
colorItem.colorIsValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: envMenu
|
||||||
|
function onEnvParametersChanged() {
|
||||||
|
colorItem.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvMenuItem {
|
||||||
|
envName: qsTr("Studio")
|
||||||
|
envStr: "SkyBox=preview_studio"
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvMenuItem {
|
||||||
|
envName: qsTr("Landscape")
|
||||||
|
envStr: "SkyBox=preview_landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorEditorPopup {
|
||||||
|
id: colorPopup
|
||||||
|
|
||||||
|
currentColor: colorItem.color
|
||||||
|
onActivateColor: (color) => colorItem.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
component EnvMenuItem: StudioControls.MenuItemWithIcon {
|
||||||
|
required property string envName
|
||||||
|
property string envStr
|
||||||
|
|
||||||
|
function commit() {
|
||||||
|
root.backend.previewEnv = envStr
|
||||||
|
}
|
||||||
|
|
||||||
|
text: envName
|
||||||
|
onClicked: commit()
|
||||||
|
checkable: false
|
||||||
|
checked: root.backend.previewEnv === envStr
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import HelperWidgets 2.0
|
||||||
|
import HelperWidgets 2.0 as HelperWidgets
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||||
|
|
||||||
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
|
height: StudioTheme.Values.toolbarHeight
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: row
|
||||||
|
spacing: StudioTheme.Values.toolbarSpacing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
leftPadding: 6
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.apply_medium
|
||||||
|
tooltip: qsTr("Apply material to selected model.")
|
||||||
|
onClicked: root.backend.toolBarAction(QmlMaterialNodeProxy.ApplyToSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.create_medium
|
||||||
|
tooltip: qsTr("Create new material.")
|
||||||
|
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.AddNewMaterial)
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.delete_medium
|
||||||
|
tooltip: qsTr("Delete current material.")
|
||||||
|
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.DeleteCurrentMaterial)
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.materialBrowser_medium
|
||||||
|
tooltip: qsTr("Open material browser.")
|
||||||
|
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.OpenMaterialBrowser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import HelperWidgets 2.0 as HelperWidgets
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
StudioControls.SplitView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||||
|
property alias showImage: previewLoader.active
|
||||||
|
property Component previewComponent: null
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight
|
||||||
|
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: previewLoader
|
||||||
|
|
||||||
|
SplitView.fillWidth: true
|
||||||
|
SplitView.minimumWidth: 152
|
||||||
|
SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0
|
||||||
|
SplitView.minimumHeight: previewLoader.visible ? 150 : 0
|
||||||
|
SplitView.maximumHeight: previewLoader.visible ? 600 : 0
|
||||||
|
|
||||||
|
visible: previewLoader.active && previewLoader.item
|
||||||
|
|
||||||
|
sourceComponent: root.previewComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.Section {
|
||||||
|
id: nameSection
|
||||||
|
|
||||||
|
// Section with hidden header is used so properties are aligned with the other sections' properties
|
||||||
|
hideHeader: true
|
||||||
|
SplitView.fillWidth: true
|
||||||
|
SplitView.preferredHeight: implicitHeight
|
||||||
|
SplitView.maximumHeight: implicitHeight
|
||||||
|
bottomPadding: StudioTheme.Values.sectionPadding * 2
|
||||||
|
collapsible: false
|
||||||
|
|
||||||
|
HelperWidgets.SectionLayout {
|
||||||
|
HelperWidgets.PropertyLabel { text: qsTr("Name") }
|
||||||
|
|
||||||
|
HelperWidgets.SecondColumnLayout {
|
||||||
|
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||||
|
|
||||||
|
HelperWidgets.LineEdit {
|
||||||
|
id: matName
|
||||||
|
|
||||||
|
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
width: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
placeholderText: qsTr("Material name")
|
||||||
|
showTranslateCheckBox: false
|
||||||
|
showExtendedFunctionButton: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: true
|
||||||
|
interval: 0
|
||||||
|
onTriggered: matName.backendValue = backendValues.objectName
|
||||||
|
// backendValues.objectName is not available yet without the Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
|
||||||
|
validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ }
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.ExpandingSpacer {}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.PropertyLabel { text: qsTr("Type") }
|
||||||
|
|
||||||
|
HelperWidgets.SecondColumnLayout {
|
||||||
|
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||||
|
|
||||||
|
HelperWidgets.ComboBox {
|
||||||
|
currentIndex: backend.possibleTypeIndex
|
||||||
|
model: backend.possibleTypes
|
||||||
|
showExtendedFunctionButton: false
|
||||||
|
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
enabled: backend.possibleTypes.length > 1
|
||||||
|
|
||||||
|
onActivated: changeTypeName(currentValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,137 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import HelperWidgets 2.0
|
||||||
|
import StudioControls 1.0 as StudioControls
|
||||||
|
import "Material" as Material
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
width: 420
|
||||||
|
height: 420
|
||||||
|
|
||||||
|
// invoked from C++ to refresh material preview image
|
||||||
|
signal refreshPreview()
|
||||||
|
|
||||||
|
// Called from C++ to close context menu on focus out
|
||||||
|
function closeContextMenu()
|
||||||
|
{
|
||||||
|
Controller.closeContextMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
Material.Toolbar {
|
||||||
|
id: toolbar
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings {
|
||||||
|
id: settings
|
||||||
|
|
||||||
|
property var topSection
|
||||||
|
property bool dockMode
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.SplitView {
|
||||||
|
id: splitView
|
||||||
|
|
||||||
|
readonly property bool isHorizontal: splitView.orientation == Qt.Horizontal
|
||||||
|
|
||||||
|
anchors.top: toolbar.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: parent.width
|
||||||
|
orientation: splitView.width > 1000 ? Qt.Horizontal : Qt.Vertical
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: leftSideView
|
||||||
|
|
||||||
|
SplitView.fillWidth: leftSideView.visible
|
||||||
|
SplitView.fillHeight: leftSideView.visible
|
||||||
|
SplitView.minimumWidth: leftSideView.visible ? 300 : 0
|
||||||
|
SplitView.minimumHeight: leftSideView.visible ? 300 : 0
|
||||||
|
|
||||||
|
active: splitView.isHorizontal
|
||||||
|
visible: leftSideView.active && leftSideView.item
|
||||||
|
|
||||||
|
sourceComponent: PreviewComponent {}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyEditorPane {
|
||||||
|
id: itemPane
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
SplitView.fillWidth: !leftSideView.visible
|
||||||
|
SplitView.fillHeight: true
|
||||||
|
SplitView.minimumWidth: leftSideView.visible ? 400 : 0
|
||||||
|
SplitView.maximumWidth: leftSideView.visible ? 800 : -1
|
||||||
|
|
||||||
|
headerDocked: !leftSideView.visible && settings.dockMode
|
||||||
|
|
||||||
|
headerComponent: Material.TopSection {
|
||||||
|
id: topSection
|
||||||
|
|
||||||
|
Component.onCompleted: topSection.restoreState(settings.topSection)
|
||||||
|
Component.onDestruction: settings.topSection = topSection.saveState()
|
||||||
|
previewComponent: PreviewComponent {}
|
||||||
|
showImage: !splitView.isHorizontal
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicPropertiesSection {
|
||||||
|
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||||
|
visible: !hasMultiSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: specificsTwo
|
||||||
|
|
||||||
|
property string theSource: specificQmlData
|
||||||
|
|
||||||
|
width: itemPane.width
|
||||||
|
visible: specificsTwo.theSource !== ""
|
||||||
|
sourceComponent: specificQmlComponent
|
||||||
|
|
||||||
|
onTheSourceChanged: {
|
||||||
|
specificsTwo.active = false
|
||||||
|
specificsTwo.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { // spacer
|
||||||
|
width: 1
|
||||||
|
height: 10
|
||||||
|
visible: specificsTwo.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: specificsOne
|
||||||
|
width: itemPane.width
|
||||||
|
source: specificsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialSection {
|
||||||
|
width: itemPane.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PreviewComponent : Material.Preview {
|
||||||
|
id: previewItem
|
||||||
|
|
||||||
|
pinned: settings.dockMode
|
||||||
|
showPinButton: !leftSideView.visible
|
||||||
|
onPinnedChanged: settings.dockMode = previewItem.pinned
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
|
||||||
|
function onRefreshPreview() {
|
||||||
|
previewItem.refreshPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,7 +18,7 @@ PropertyEditorPane {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
DynamicPropertiesSection {
|
DynamicPropertiesSection {
|
||||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||||
visible: !hasMultiSelection
|
visible: !hasMultiSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,8 +11,4 @@ Column {
|
|||||||
PrincipledMaterialSection {
|
PrincipledMaterialSection {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,4 @@ Column {
|
|||||||
SpecularGlossyMaterialSection {
|
SpecularGlossyMaterialSection {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import HelperWidgets 2.0
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property QmlTextureNodeProxy backend: textureNodeBackend
|
||||||
|
|
||||||
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
|
implicitHeight: StudioTheme.Values.toolbarHeight
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: row
|
||||||
|
spacing: StudioTheme.Values.toolbarSpacing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
leftPadding: 6
|
||||||
|
|
||||||
|
AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.apply_medium
|
||||||
|
enabled: backend.hasTexture && backend.selectedNodeAcceptsMaterial && hasQuick3DImport && hasMaterialLibrary
|
||||||
|
tooltip: qsTr("Apply texture to selected model's material.")
|
||||||
|
onClicked: backend.toolbarAction(QmlTextureNodeProxy.ApplyToSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.create_medium
|
||||||
|
enabled: hasQuick3DImport && hasMaterialLibrary
|
||||||
|
tooltip: qsTr("Create new texture.")
|
||||||
|
onClicked: backend.toolbarAction(QmlTextureNodeProxy.AddNewTexture)
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.delete_medium
|
||||||
|
enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary
|
||||||
|
tooltip: qsTr("Delete current texture.")
|
||||||
|
onClicked: backend.toolbarAction(QmlTextureNodeProxy.DeleteCurrentTexture)
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.materialBrowser_medium
|
||||||
|
enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary
|
||||||
|
tooltip: qsTr("Open material browser.")
|
||||||
|
onClicked: backend.toolbarAction(QmlTextureNodeProxy.OpenMaterialBrowser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import HelperWidgets 2.0 as HelperWidgets
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property HelperWidgets.QmlTextureNodeProxy backend: textureNodeBackend
|
||||||
|
|
||||||
|
readonly property string sourcePath: backendValues.source ? backendValues.source.valueToString : ""
|
||||||
|
readonly property string previewPath: "image://qmldesigner_thumbnails/" + backend.resolveResourcePath(root.sourcePath)
|
||||||
|
|
||||||
|
function refreshPreview()
|
||||||
|
{
|
||||||
|
texturePreview.source = ""
|
||||||
|
texturePreview.source = root.previewPath
|
||||||
|
}
|
||||||
|
|
||||||
|
color: StudioTheme.Values.themePanelBackground
|
||||||
|
implicitHeight: column.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: column
|
||||||
|
|
||||||
|
Item { implicitWidth: 1; implicitHeight: 10 } // spacer
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: previewRect
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: 152
|
||||||
|
height: 152
|
||||||
|
color: "#000000"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: texturePreview
|
||||||
|
asynchronous: true
|
||||||
|
width: 150
|
||||||
|
height: 150
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: root.previewPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.Section {
|
||||||
|
id: nameSection
|
||||||
|
|
||||||
|
// Section with hidden header is used so properties are aligned with the other sections' properties
|
||||||
|
hideHeader: true
|
||||||
|
implicitWidth: root.width
|
||||||
|
bottomPadding: StudioTheme.Values.sectionPadding * 2
|
||||||
|
collapsible: false
|
||||||
|
|
||||||
|
HelperWidgets.SectionLayout {
|
||||||
|
HelperWidgets.PropertyLabel { text: qsTr("Name") }
|
||||||
|
|
||||||
|
HelperWidgets.SecondColumnLayout {
|
||||||
|
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||||
|
|
||||||
|
HelperWidgets.LineEdit {
|
||||||
|
id: texName
|
||||||
|
|
||||||
|
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
width: StudioTheme.Values.singleControlColumnWidth
|
||||||
|
placeholderText: qsTr("Texture name")
|
||||||
|
showTranslateCheckBox: false
|
||||||
|
showExtendedFunctionButton: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: true
|
||||||
|
interval: 0
|
||||||
|
onTriggered: texName.backendValue = backendValues.objectName
|
||||||
|
// backendValues.objectName is not available yet without the Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
|
||||||
|
validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ }
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.ExpandingSpacer {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import HelperWidgets 2.0
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
import "Texture" as Texture
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: itemPane
|
||||||
|
|
||||||
|
width: 420
|
||||||
|
height: 420
|
||||||
|
color: StudioTheme.Values.themePanelBackground
|
||||||
|
|
||||||
|
// invoked from C++ to refresh material preview image
|
||||||
|
function refreshPreview()
|
||||||
|
{
|
||||||
|
topSection.refreshPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from C++ to close context menu on focus out
|
||||||
|
function closeContextMenu()
|
||||||
|
{
|
||||||
|
Controller.closeContextMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Texture.ToolBar {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture.TopSection {
|
||||||
|
id: topSection
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyEditorPane {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
DynamicPropertiesSection {
|
||||||
|
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||||
|
visible: !hasMultiSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: specificsTwo
|
||||||
|
|
||||||
|
property string theSource: specificQmlData
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
visible: specificsTwo.theSource !== ""
|
||||||
|
sourceComponent: specificQmlComponent
|
||||||
|
|
||||||
|
onTheSourceChanged: {
|
||||||
|
specificsTwo.active = false
|
||||||
|
specificsTwo.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: 10
|
||||||
|
visible: specificsTwo.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: specificsOne
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
source: specificsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureSection {
|
||||||
|
width: itemPane.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +0,0 @@
|
|||||||
// Copyright (C) 2021 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import HelperWidgets 2.0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
TextureSection {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
@@ -507,6 +507,8 @@ Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PropertyLabel {
|
PropertyLabel {
|
||||||
|
readonly property bool __inDynamicPropertiesSection: true
|
||||||
|
|
||||||
text: propertyName
|
text: propertyName
|
||||||
tooltip: propertyType
|
tooltip: propertyType
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
@@ -24,9 +24,7 @@ Item {
|
|||||||
property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth
|
property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth
|
||||||
property real __actionIndicatorHeight: StudioTheme.Values.height
|
property real __actionIndicatorHeight: StudioTheme.Values.height
|
||||||
property string typeFilter: "QtQuick3D.Material"
|
property string typeFilter: "QtQuick3D.Material"
|
||||||
// This binding is a workaround to overcome the rather long adaption to new Qt versions. This
|
property string textRole: "id"
|
||||||
// should actually be fixed in the ModelSection.qml by setting the textRole: "idAndName".
|
|
||||||
property string textRole: (root.typeFilter === "QtQuick3D.Material") ? "idAndName" : "id"
|
|
||||||
property string valueRole: "id"
|
property string valueRole: "id"
|
||||||
property int activatedReason: ComboBox.ActivatedReason.Other
|
property int activatedReason: ComboBox.ActivatedReason.Other
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ Rectangle {
|
|||||||
|
|
||||||
default property alias content: mainColumn.children
|
default property alias content: mainColumn.children
|
||||||
property alias scrollView: mainScrollView
|
property alias scrollView: mainScrollView
|
||||||
|
property alias searchBar: propertySearchBar
|
||||||
|
|
||||||
property bool headerDocked: false
|
property bool headerDocked: false
|
||||||
readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item
|
readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item
|
||||||
@@ -29,10 +30,23 @@ Rectangle {
|
|||||||
Controller.closeContextMenu()
|
Controller.closeContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called from C++ to clear the search when the selected node changes
|
||||||
|
function clearSearch() {
|
||||||
|
propertySearchBar.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertySearchBar {
|
||||||
|
id: propertySearchBar
|
||||||
|
|
||||||
|
contentItem: mainColumn
|
||||||
|
width: parent.width
|
||||||
|
z: parent.z + 1
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dockedHeaderLoader
|
id: dockedHeaderLoader
|
||||||
|
|
||||||
anchors.top: itemPane.top
|
anchors.top: propertySearchBar.bottom
|
||||||
z: parent.z + 1
|
z: parent.z + 1
|
||||||
height: item ? item.implicitHeight : 0
|
height: item ? item.implicitHeight : 0
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -123,6 +137,16 @@ Rectangle {
|
|||||||
HeaderBackground{}
|
HeaderBackground{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
|
||||||
|
visible: propertySearchBar.hasDoneSearch && !propertySearchBar.hasMatchSearch
|
||||||
|
text: qsTr("No match found.")
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
font.pixelSize: StudioTheme.Values.baseFont
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
|
|
||||||
|
@@ -9,10 +9,13 @@ import StudioTheme 1.0 as StudioTheme
|
|||||||
T.Label {
|
T.Label {
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
|
readonly property bool __isPropertyLabel: true // used by property search logic
|
||||||
|
|
||||||
property alias tooltip: toolTipArea.tooltip
|
property alias tooltip: toolTipArea.tooltip
|
||||||
|
|
||||||
property bool blockedByContext: false
|
property bool blockedByContext: false
|
||||||
property bool blockedByTemplate: false // MCU
|
property bool blockedByTemplate: false // MCU
|
||||||
|
property bool searchNoMatch: false
|
||||||
|
|
||||||
width: StudioTheme.Values.propertyLabelWidth
|
width: StudioTheme.Values.propertyLabelWidth
|
||||||
color: StudioTheme.Values.themeTextColor
|
color: StudioTheme.Values.themeTextColor
|
||||||
@@ -34,6 +37,14 @@ T.Label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
|
State {
|
||||||
|
name: "searchNoMatch"
|
||||||
|
when: searchNoMatch
|
||||||
|
PropertyChanges {
|
||||||
|
target: label
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
State {
|
State {
|
||||||
name: "disabled"
|
name: "disabled"
|
||||||
when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate)
|
when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate)
|
||||||
|
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property alias hasMatchSearch: internal.matched
|
||||||
|
readonly property alias hasDoneSearch: internal.searched
|
||||||
|
|
||||||
|
property Item contentItem: null
|
||||||
|
|
||||||
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
|
height: StudioTheme.Values.toolbarHeight
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
internal.clear();
|
||||||
|
searchBox.text = "";
|
||||||
|
internal.timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
internal.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.SearchBox {
|
||||||
|
id: searchBox
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||||
|
anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||||
|
anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||||
|
anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||||
|
style: StudioTheme.Values.searchControlStyle
|
||||||
|
onSearchChanged: internal.timer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: internal
|
||||||
|
|
||||||
|
readonly property var reverts: []
|
||||||
|
readonly property Timer timer: Timer {
|
||||||
|
interval: 300
|
||||||
|
repeat: false
|
||||||
|
|
||||||
|
onTriggered: internal.search()
|
||||||
|
}
|
||||||
|
property bool matched: false
|
||||||
|
property bool searched: false
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
internal.reverts.forEach(revert => revert());
|
||||||
|
internal.reverts.length = 0;
|
||||||
|
internal.matched = false;
|
||||||
|
internal.searched = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
internal.clear();
|
||||||
|
const searchText = searchBox.text.toLowerCase();
|
||||||
|
if (searchText.length > 0) {
|
||||||
|
internal.traverse(root.contentItem, searchText);
|
||||||
|
internal.searched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableSearchNoMatchAction(item) {
|
||||||
|
item.searchNoMatch = true;
|
||||||
|
internal.reverts.push(() => {
|
||||||
|
item.searchNoMatch = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableVisibleAction(item) {
|
||||||
|
item.visible = false;
|
||||||
|
internal.reverts.push(() => {
|
||||||
|
item.visible = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableSearchHideAction(item) {
|
||||||
|
item.searchHide = true;
|
||||||
|
internal.reverts.push(() => {
|
||||||
|
item.searchHide = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandSectionAction(item) {
|
||||||
|
internal.matched = true;
|
||||||
|
const prevValue = item.expanded;
|
||||||
|
item.expanded = true;
|
||||||
|
internal.reverts.push(() => {
|
||||||
|
item.expanded = prevValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverse(item, searchText) {
|
||||||
|
let hideSection = true;
|
||||||
|
let hideParentSection = true;
|
||||||
|
item.children.forEach((child, index, arr) => {
|
||||||
|
if (!child.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (child.__isPropertyLabel) {
|
||||||
|
const propertyLabel = child;
|
||||||
|
const text = propertyLabel.text.toLowerCase();
|
||||||
|
if (!text.includes(searchText)) {
|
||||||
|
internal.disableSearchNoMatchAction(propertyLabel);
|
||||||
|
const action = propertyLabel.__inDynamicPropertiesSection ? internal.disableVisibleAction
|
||||||
|
: internal.disableSearchNoMatchAction;
|
||||||
|
const nextItem = arr[index + 1];
|
||||||
|
action(nextItem);
|
||||||
|
} else {
|
||||||
|
hideSection = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hideSection &= internal.traverse(child, searchText);
|
||||||
|
if (child.__isSection) {
|
||||||
|
const action = hideSection ? internal.enableSearchHideAction
|
||||||
|
: internal.expandSectionAction;
|
||||||
|
action(child);
|
||||||
|
|
||||||
|
if (child.__isInEffectsSection && !hideSection)
|
||||||
|
hideParentSection = false;
|
||||||
|
|
||||||
|
hideSection = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return hideParentSection && hideSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,21 @@ import QtQuick 2.15
|
|||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool searchNoMatch: false
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "searchNoMatch"
|
||||||
|
when: searchNoMatch
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,8 @@ import StudioTheme as StudioTheme
|
|||||||
Item {
|
Item {
|
||||||
id: section
|
id: section
|
||||||
|
|
||||||
|
readonly property bool __isSection: true // used by property search logic
|
||||||
|
|
||||||
property string caption: "Title"
|
property string caption: "Title"
|
||||||
property color labelColor: StudioTheme.Values.themeTextColor
|
property color labelColor: StudioTheme.Values.themeTextColor
|
||||||
property int labelCapitalization: Font.AllUppercase
|
property int labelCapitalization: Font.AllUppercase
|
||||||
@@ -58,6 +60,7 @@ Item {
|
|||||||
property bool dropEnabled: false
|
property bool dropEnabled: false
|
||||||
property bool highlight: false
|
property bool highlight: false
|
||||||
property bool eyeEnabled: true // eye button enabled (on)
|
property bool eyeEnabled: true // eye button enabled (on)
|
||||||
|
property bool searchHide: false
|
||||||
|
|
||||||
property bool useDefaulContextMenu: true
|
property bool useDefaulContextMenu: true
|
||||||
|
|
||||||
@@ -343,6 +346,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
|
State {
|
||||||
|
name: "Hide"
|
||||||
|
when: section.searchHide
|
||||||
|
PropertyChanges {
|
||||||
|
target: section
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
State {
|
State {
|
||||||
name: "Collapsed"
|
name: "Collapsed"
|
||||||
when: !section.expanded
|
when: !section.expanded
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
module HelperWidgets
|
||||||
AbstractButton 2.0 AbstractButton.qml
|
AbstractButton 2.0 AbstractButton.qml
|
||||||
ActionIndicator 2.0 ActionIndicator.qml
|
ActionIndicator 2.0 ActionIndicator.qml
|
||||||
AlignmentHorizontalButtons 2.0 AlignmentHorizontalButtons.qml
|
AlignmentHorizontalButtons 2.0 AlignmentHorizontalButtons.qml
|
||||||
@@ -51,6 +52,7 @@ MultiIconLabel 2.0 MultiIconLabel.qml
|
|||||||
OriginControl 2.0 OriginControl.qml
|
OriginControl 2.0 OriginControl.qml
|
||||||
OriginIndicator 2.0 OriginIndicator.qml
|
OriginIndicator 2.0 OriginIndicator.qml
|
||||||
OriginSelector 2.0 OriginSelector.qml
|
OriginSelector 2.0 OriginSelector.qml
|
||||||
|
PopupLabel 2.0 PopupLabel.qml
|
||||||
PropertyEditorPane 2.0 PropertyEditorPane.qml
|
PropertyEditorPane 2.0 PropertyEditorPane.qml
|
||||||
PropertyLabel 2.0 PropertyLabel.qml
|
PropertyLabel 2.0 PropertyLabel.qml
|
||||||
PaddingSection 2.0 PaddingSection.qml
|
PaddingSection 2.0 PaddingSection.qml
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Templates as T
|
import QtQuick.Templates as T
|
||||||
import StudioTheme 1.0 as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: control
|
id: control
|
||||||
|
|
||||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
@@ -18,8 +18,6 @@ Rectangle {
|
|||||||
property bool pressed: false
|
property bool pressed: false
|
||||||
property bool forceVisible: false
|
property bool forceVisible: false
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
implicitWidth: control.style.actionIndicatorSize.width
|
implicitWidth: control.style.actionIndicatorSize.width
|
||||||
implicitHeight: control.style.actionIndicatorSize.height
|
implicitHeight: control.style.actionIndicatorSize.height
|
||||||
|
|
||||||
|
@@ -3,27 +3,29 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Templates as T
|
import QtQuick.Templates as T
|
||||||
import StudioTheme 1.0 as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
|
||||||
T.Button {
|
T.Button {
|
||||||
id: control
|
id: control
|
||||||
|
|
||||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
implicitWidth: Math.max(buttonBackground ? buttonBackground.implicitWidth : 0,
|
implicitWidth: Math.max(control.style.squareControlSize.width,
|
||||||
textItem.implicitWidth + leftPadding + rightPadding)
|
implicitBackgroundWidth + leftInset + rightInset,
|
||||||
implicitHeight: Math.max(buttonBackground ? buttonBackground.implicitHeight : 0,
|
implicitContentWidth + leftPadding + rightPadding)
|
||||||
textItem.implicitHeight + topPadding + bottomPadding)
|
implicitHeight: Math.max(control.style.squareControlSize.height,
|
||||||
|
implicitBackgroundHeight + topInset + bottomInset,
|
||||||
|
implicitContentHeight + topPadding + bottomPadding)
|
||||||
|
|
||||||
leftPadding: control.style.dialogPadding
|
leftPadding: control.style.dialogPadding
|
||||||
rightPadding: control.style.dialogPadding
|
rightPadding: control.style.dialogPadding
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
id: buttonBackground
|
id: controlBackground
|
||||||
implicitWidth: 70
|
|
||||||
implicitHeight: 20
|
|
||||||
color: control.style.background.idle
|
color: control.style.background.idle
|
||||||
border.color: control.style.border.idle
|
border.color: control.style.border.idle
|
||||||
anchors.fill: parent
|
border.width: control.style.borderWidth
|
||||||
|
radius: control.style.radius
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Text {
|
contentItem: Text {
|
||||||
@@ -41,7 +43,7 @@ T.Button {
|
|||||||
when: control.enabled && !control.down && !control.hovered && !control.checked
|
when: control.enabled && !control.down && !control.hovered && !control.checked
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: buttonBackground
|
target: controlBackground
|
||||||
color: control.highlighted ? control.style.interaction
|
color: control.highlighted ? control.style.interaction
|
||||||
: control.style.background.idle
|
: control.style.background.idle
|
||||||
border.color: control.style.border.idle
|
border.color: control.style.border.idle
|
||||||
@@ -56,7 +58,7 @@ T.Button {
|
|||||||
when: control.enabled && control.hovered && !control.checked && !control.down
|
when: control.enabled && control.hovered && !control.checked && !control.down
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: buttonBackground
|
target: controlBackground
|
||||||
color: control.style.background.hover
|
color: control.style.background.hover
|
||||||
border.color: control.style.border.hover
|
border.color: control.style.border.hover
|
||||||
}
|
}
|
||||||
@@ -70,9 +72,9 @@ T.Button {
|
|||||||
when: control.enabled && (control.checked || control.down)
|
when: control.enabled && (control.checked || control.down)
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: buttonBackground
|
target: controlBackground
|
||||||
color: control.style.background.interaction
|
color: control.style.interaction
|
||||||
border.color: control.style.border.interaction
|
border.color: control.style.interaction
|
||||||
}
|
}
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: textItem
|
target: textItem
|
||||||
@@ -83,7 +85,7 @@ T.Button {
|
|||||||
name: "disable"
|
name: "disable"
|
||||||
when: !control.enabled
|
when: !control.enabled
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: buttonBackground
|
target: controlBackground
|
||||||
color: control.style.background.disabled
|
color: control.style.background.disabled
|
||||||
border.color: control.style.border.disabled
|
border.color: control.style.border.disabled
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
module StudioControls
|
||||||
AbstractButton 1.0 AbstractButton.qml
|
AbstractButton 1.0 AbstractButton.qml
|
||||||
ActionIndicator 1.0 ActionIndicator.qml
|
ActionIndicator 1.0 ActionIndicator.qml
|
||||||
Button 1.0 Button.qml
|
Button 1.0 Button.qml
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
module StudioTheme
|
||||||
singleton Values 1.0 Values.qml
|
singleton Values 1.0 Values.qml
|
||||||
singleton Constants 1.0 Constants.qml
|
singleton Constants 1.0 Constants.qml
|
||||||
ControlStyle 1.0 ControlStyle.qml
|
ControlStyle 1.0 ControlStyle.qml
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (C) 2025 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import StudioControls as StudioControls
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
import ScriptEditorBackend
|
||||||
|
|
||||||
|
StudioControls.TopLevelComboBox {
|
||||||
|
id: action
|
||||||
|
|
||||||
|
style: StudioTheme.Values.connectionPopupControlStyle
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
|
||||||
|
property var backend
|
||||||
|
property int indexFromBackend: indexOfValue(backend.actionType)
|
||||||
|
|
||||||
|
onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend
|
||||||
|
onActivated: backend.changeActionType(action.currentValue)
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.CallFunction
|
||||||
|
text: qsTr("Call Function")
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.Assign
|
||||||
|
text: qsTr("Assign")
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.ChangeState
|
||||||
|
text: qsTr("Change State")
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.SetProperty
|
||||||
|
text: qsTr("Set Property")
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.PrintMessage
|
||||||
|
text: qsTr("Print Message")
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
value: StatementDelegate.Custom
|
||||||
|
text: qsTr("Custom")
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,13 +4,16 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
|
import ScriptEditorBackend
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
property var conditionListModel: ConnectionsEditorEditorBackend.connectionModel.delegate.conditionListModel
|
property var conditionListModel: ScriptEditorBackend.conditionListModel // connect a valid model here
|
||||||
|
property alias popupListModel: popup.listModel
|
||||||
|
property alias popupTreeModel: popup.treeModel
|
||||||
|
|
||||||
property alias model: repeater.model
|
property alias model: repeater.model
|
||||||
property int shadowPillIndex: -1
|
property int shadowPillIndex: -1
|
@@ -5,6 +5,7 @@ import QtQuick
|
|||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
|
import ScriptEditorBackend
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: root
|
id: root
|
@@ -0,0 +1,262 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import HelperWidgets as HelperWidgets
|
||||||
|
import StudioTheme as StudioTheme
|
||||||
|
import ScriptEditorBackend
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real horizontalSpacing
|
||||||
|
property real verticalSpacing
|
||||||
|
property real columnWidth
|
||||||
|
|
||||||
|
property var backend
|
||||||
|
|
||||||
|
property alias keepOpen: expressionDialogLoader.visible
|
||||||
|
|
||||||
|
property int currentAction: StatementDelegate.Custom
|
||||||
|
|
||||||
|
property string itemTooltip: qsTr("Sets the component that is affected by the action.")
|
||||||
|
property string methodTooltip: qsTr("Sets the item component's method.")
|
||||||
|
property string fromTooltip: qsTr("Sets the component and its property from which the value is copied.")
|
||||||
|
property string toTooltip: qsTr("Sets the component and its property to which the copied value is assigned.")
|
||||||
|
property string addConditionTooltip: qsTr("Sets a logical condition for the selected action.")
|
||||||
|
property string removeConditionTooltip: qsTr("Removes the logical condition for the action.")
|
||||||
|
property string stateGroupTooltip: qsTr("Sets a <b>State Group</b> that is accessed when the action is initiated.")
|
||||||
|
property string stateTooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the action is initiated.")
|
||||||
|
property string propertyTooltip: qsTr("Sets the property of the component that is affected by the action.")
|
||||||
|
property string valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action.")
|
||||||
|
property string messageTooltip: qsTr("Sets a text that is printed when the action is initiated.")
|
||||||
|
|
||||||
|
|
||||||
|
StatementEditor {
|
||||||
|
width: root.width
|
||||||
|
actionType: root.currentAction
|
||||||
|
horizontalSpacing: root.horizontalSpacing
|
||||||
|
columnWidth: root.columnWidth
|
||||||
|
statement: root.backend.okStatement
|
||||||
|
backend: root.backend
|
||||||
|
spacing: root.verticalSpacing
|
||||||
|
itemTooltip: root.itemTooltip
|
||||||
|
methodTooltip: root.methodTooltip
|
||||||
|
fromTooltip: root.fromTooltip
|
||||||
|
toTooltip: root.toTooltip
|
||||||
|
stateGroupTooltip: root.stateGroupTooltip
|
||||||
|
stateTooltip: root.stateTooltip
|
||||||
|
propertyTooltip: root.propertyTooltip
|
||||||
|
valueTooltip: root.valueTooltip
|
||||||
|
messageTooltip: root.messageTooltip
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: addConditionButton
|
||||||
|
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||||
|
width: 160
|
||||||
|
buttonIcon: qsTr("Add Condition")
|
||||||
|
tooltip: root.addConditionTooltip
|
||||||
|
iconSize: StudioTheme.Values.baseFontSize
|
||||||
|
iconFontFamily: StudioTheme.Constants.font.family
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: root.currentAction !== StatementDelegate.Custom && !backend.hasCondition
|
||||||
|
|
||||||
|
onClicked: backend.addCondition()
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||||
|
width: 160
|
||||||
|
buttonIcon: qsTr("Remove Condition")
|
||||||
|
tooltip: root.removeConditionTooltip
|
||||||
|
iconSize: StudioTheme.Values.baseFontSize
|
||||||
|
iconFontFamily: StudioTheme.Constants.font.family
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: root.currentAction !== StatementDelegate.Custom && backend.hasCondition
|
||||||
|
|
||||||
|
onClicked: backend.removeCondition()
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionBuilder {
|
||||||
|
style: StudioTheme.Values.connectionPopupControlStyle
|
||||||
|
width: root.width
|
||||||
|
|
||||||
|
visible: backend.hasCondition
|
||||||
|
model: backend.conditionListModel
|
||||||
|
conditionListModel: backend.conditionListModel
|
||||||
|
popupListModel: backend.propertyListProxyModel
|
||||||
|
popupTreeModel: backend.propertyTreeModel
|
||||||
|
|
||||||
|
onRemove: function(index) {
|
||||||
|
backend.conditionListModel.removeToken(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate: function(index, value) {
|
||||||
|
backend.conditionListModel.updateToken(index, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd: function(value) {
|
||||||
|
backend.conditionListModel.appendToken(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onInsert: function(index, value, type) {
|
||||||
|
if (type === ConditionListModel.Intermediate)
|
||||||
|
backend.conditionListModel.insertIntermediateToken(index, value)
|
||||||
|
else if (type === ConditionListModel.Shadow)
|
||||||
|
backend.conditionListModel.insertShadowToken(index, value)
|
||||||
|
else
|
||||||
|
backend.conditionListModel.insertToken(index, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSetValue: function(index, value) {
|
||||||
|
backend.conditionListModel.setShadowToken(index, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||||
|
width: 160
|
||||||
|
buttonIcon: qsTr("Add Else Statement")
|
||||||
|
tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.")
|
||||||
|
iconSize: StudioTheme.Values.baseFontSize
|
||||||
|
iconFontFamily: StudioTheme.Constants.font.family
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: root.currentAcion !== StatementDelegate.Custom
|
||||||
|
&& backend.hasCondition && !backend.hasElse
|
||||||
|
|
||||||
|
onClicked: backend.addElse()
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||||
|
width: 160
|
||||||
|
buttonIcon: qsTr("Remove Else Statement")
|
||||||
|
tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.")
|
||||||
|
iconSize: StudioTheme.Values.baseFontSize
|
||||||
|
iconFontFamily: StudioTheme.Constants.font.family
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: root.currentAction !== StatementDelegate.Custom
|
||||||
|
&& backend.hasCondition && backend.hasElse
|
||||||
|
|
||||||
|
onClicked: backend.removeElse()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Else Statement
|
||||||
|
StatementEditor {
|
||||||
|
width: root.width
|
||||||
|
actionType: root.currentAction
|
||||||
|
horizontalSpacing: root.horizontalSpacing
|
||||||
|
columnWidth: root.columnWidth
|
||||||
|
statement: backend.koStatement
|
||||||
|
backend: root.backend
|
||||||
|
spacing: root.verticalSpacing
|
||||||
|
visible: action.currentValue !== StatementDelegate.Custom
|
||||||
|
&& backend.hasCondition && backend.hasElse
|
||||||
|
itemTooltip: root.itemTooltip
|
||||||
|
methodTooltip: root.methodTooltip
|
||||||
|
fromTooltip: root.fromTooltip
|
||||||
|
toTooltip: root.toTooltip
|
||||||
|
stateGroupTooltip: root.stateGroupTooltip
|
||||||
|
stateTooltip: root.stateTooltip
|
||||||
|
propertyTooltip: root.propertyTooltip
|
||||||
|
valueTooltip: root.valueTooltip
|
||||||
|
messageTooltip: root.messageTooltip
|
||||||
|
}
|
||||||
|
|
||||||
|
// code preview toolbar
|
||||||
|
Column {
|
||||||
|
id: miniToolbarEditor
|
||||||
|
width: parent.width
|
||||||
|
spacing: -2
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: miniToolbar
|
||||||
|
width: parent.width
|
||||||
|
height: editorButton.height + 2
|
||||||
|
radius: 4
|
||||||
|
z: -1
|
||||||
|
color: StudioTheme.Values.themeConnectionEditorMicroToolbar
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 2
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: editorButton
|
||||||
|
style: StudioTheme.Values.microToolbarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.codeEditor_medium
|
||||||
|
tooltip: qsTr("Write the conditions manually.")
|
||||||
|
onClicked: expressionDialogLoader.show()
|
||||||
|
}
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
id: jumpToCodeButton
|
||||||
|
style: StudioTheme.Values.microToolbarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.jumpToCode_medium
|
||||||
|
tooltip: qsTr("Jump to the code.")
|
||||||
|
onClicked: backend.jumpToCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
Rectangle {
|
||||||
|
id: editor
|
||||||
|
width: parent.width
|
||||||
|
height: 150
|
||||||
|
color: StudioTheme.Values.themeConnectionCodeEditor
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: code
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
text: backend.indentedSource
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
font.pixelSize: StudioTheme.Values.myFontSize
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: expressionDialogLoader
|
||||||
|
parent: editor
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: false
|
||||||
|
active: visible
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
expressionDialogLoader.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: Item {
|
||||||
|
id: bindingEditorParent
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
bindingEditor.showWidget()
|
||||||
|
bindingEditor.text = backend.source
|
||||||
|
bindingEditor.showControls(false)
|
||||||
|
bindingEditor.setMultilne(true)
|
||||||
|
bindingEditor.updateWindowName()
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.ActionEditor {
|
||||||
|
id: bindingEditor
|
||||||
|
|
||||||
|
onRejected: {
|
||||||
|
bindingEditor.hideWidget()
|
||||||
|
expressionDialogLoader.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
backend.setNewSource(bindingEditor.text)
|
||||||
|
bindingEditor.hideWidget()
|
||||||
|
expressionDialogLoader.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,7 +6,7 @@ import QtQuick.Controls
|
|||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
import ConnectionsEditorEditorBackend
|
import ScriptEditorBackend
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
@@ -20,26 +20,37 @@ Column {
|
|||||||
|
|
||||||
property var backend
|
property var backend
|
||||||
|
|
||||||
|
property string itemTooltip
|
||||||
|
property alias methodTooltip: methodPopupLabel.tooltip
|
||||||
|
property alias fromTooltip: fromPopupLabel.tooltip
|
||||||
|
property alias toTooltip: toPopupLabel.tooltip
|
||||||
|
property alias stateGroupTooltip: stateGroupPopupLabel.tooltip
|
||||||
|
property alias stateTooltip: statePopupLabel.tooltip
|
||||||
|
property alias propertyTooltip: propertyPopupLabel.tooltip
|
||||||
|
property alias valueTooltip: valuePopupLabel.tooltip
|
||||||
|
property alias messageTooltip: messagePopupLabel.tooltip
|
||||||
|
|
||||||
// Call Function
|
// Call Function
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.CallFunction
|
visible: root.actionType === StatementDelegate.CallFunction
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: itemPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Item")
|
text: qsTr("Item")
|
||||||
tooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
tooltip: root.itemTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: methodPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Method")
|
text: qsTr("Method")
|
||||||
tooltip: qsTr("Sets the item component's method that is affected by the <b>Target</b> component's <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.CallFunction
|
visible: root.actionType === StatementDelegate.CallFunction
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
StudioControls.TopLevelComboBox {
|
||||||
@@ -68,23 +79,23 @@ Column {
|
|||||||
|
|
||||||
// Assign
|
// Assign
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
visible: root.actionType === StatementDelegate.Assign
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: fromPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("From")
|
text: qsTr("From")
|
||||||
tooltip: qsTr("Sets the component and its property from which the value is copied when the <b>Target</b> component initiates the <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: toPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("To")
|
text: qsTr("To")
|
||||||
tooltip: qsTr("Sets the component and its property to which the copied value is assigned when the <b>Target</b> component initiates the <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
visible: root.actionType === StatementDelegate.Assign
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
StudioControls.TopLevelComboBox {
|
||||||
@@ -114,7 +125,7 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
visible: root.actionType === StatementDelegate.Assign
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
StudioControls.TopLevelComboBox {
|
||||||
@@ -145,24 +156,24 @@ Column {
|
|||||||
|
|
||||||
// Change State
|
// Change State
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.ChangeState
|
visible: root.actionType === StatementDelegate.ChangeState
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: stateGroupPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("State Group")
|
text: qsTr("State Group")
|
||||||
tooltip: qsTr("Sets a <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: statePopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("State")
|
text: qsTr("State")
|
||||||
tooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.ChangeState
|
visible: root.actionType === StatementDelegate.ChangeState
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
StudioControls.TopLevelComboBox {
|
||||||
@@ -191,24 +202,24 @@ Column {
|
|||||||
|
|
||||||
// Set Property
|
// Set Property
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
visible: root.actionType === StatementDelegate.SetProperty
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Item")
|
text: qsTr("Item")
|
||||||
tooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
tooltip: root.itemTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: propertyPopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
text: qsTr("Property")
|
text: qsTr("Property")
|
||||||
tooltip: qsTr("Sets the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
visible: root.actionType === StatementDelegate.SetProperty
|
||||||
spacing: root.horizontalSpacing
|
spacing: root.horizontalSpacing
|
||||||
|
|
||||||
StudioControls.TopLevelComboBox {
|
StudioControls.TopLevelComboBox {
|
||||||
@@ -236,16 +247,16 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: valuePopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
visible: root.actionType === StatementDelegate.SetProperty
|
||||||
text: qsTr("Value")
|
text: qsTr("Value")
|
||||||
tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StudioControls.TextField {
|
StudioControls.TextField {
|
||||||
id: setPropertyArgument
|
id: setPropertyArgument
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
visible: root.actionType === StatementDelegate.SetProperty
|
||||||
width: root.width
|
width: root.width
|
||||||
actionIndicatorVisible: false
|
actionIndicatorVisible: false
|
||||||
translationIndicatorVisible: false
|
translationIndicatorVisible: false
|
||||||
@@ -257,16 +268,16 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print Message
|
// Print Message
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
|
id: messagePopupLabel
|
||||||
width: root.columnWidth
|
width: root.columnWidth
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage
|
visible: root.actionType === StatementDelegate.PrintMessage
|
||||||
text: qsTr("Message")
|
text: qsTr("Message")
|
||||||
tooltip: qsTr("Sets a text that is printed when the <b>Signal</b> of the <b>Target</b> component initiates.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StudioControls.TextField {
|
StudioControls.TextField {
|
||||||
id: messageString
|
id: messageString
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage
|
visible: root.actionType === StatementDelegate.PrintMessage
|
||||||
width: root.width
|
width: root.width
|
||||||
actionIndicatorVisible: false
|
actionIndicatorVisible: false
|
||||||
translationIndicatorVisible: false
|
translationIndicatorVisible: false
|
||||||
@@ -277,8 +288,8 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom
|
// Custom
|
||||||
PopupLabel {
|
HelperWidgets.PopupLabel {
|
||||||
visible: root.actionType === ConnectionModelStatementDelegate.Custom
|
visible: root.actionType === StatementDelegate.Custom
|
||||||
text: qsTr("Custom Connections can only be edited with the binding editor")
|
text: qsTr("Custom Connections can only be edited with the binding editor")
|
||||||
width: root.width
|
width: root.width
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
@@ -6,15 +6,15 @@ import QtQuick.Controls as Controls
|
|||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
import ConnectionsEditorEditorBackend
|
import ScriptEditorBackend
|
||||||
|
|
||||||
Controls.Popup {
|
Controls.Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||||
|
|
||||||
property var listModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyListProxyModel
|
property var listModel: ScriptEditorBackend.propertyListProxyModel // connect a valid model here
|
||||||
property var treeModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyTreeModel
|
property var treeModel: ScriptEditorBackend.propertyTreeModel // connect a valid model here
|
||||||
|
|
||||||
signal select(var value)
|
signal select(var value)
|
||||||
signal entered(var value)
|
signal entered(var value)
|
@@ -0,0 +1,2 @@
|
|||||||
|
ScriptEditorForm 1.0 ScriptEditorForm.qml
|
||||||
|
ActionsComboBox 1.0 ActionsComboBox.qml
|
@@ -26,8 +26,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls.Basic as Basic
|
import QtQuick.Controls.Basic as Basic
|
||||||
import StatesEditor
|
import StatesEditor
|
||||||
import HelperWidgets 2.0 as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls 1.0 as StudioControls
|
import StudioControls as StudioControls
|
||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
import StatesEditorBackend
|
import StatesEditorBackend
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ Rectangle {
|
|||||||
StudioControls.Dialog {
|
StudioControls.Dialog {
|
||||||
id: editDialog
|
id: editDialog
|
||||||
title: qsTr("Rename state group")
|
title: qsTr("Rename state group")
|
||||||
standardButtons: Dialog.Apply | Dialog.Cancel
|
standardButtons: Basic.Dialog.Apply | Basic.Dialog.Cancel
|
||||||
x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width)
|
x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width)
|
||||||
y: toolBar.height
|
y: toolBar.height
|
||||||
width: Math.min(300, root.width)
|
width: Math.min(300, root.width)
|
||||||
@@ -328,15 +328,11 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
let btn = editDialog.standardButton(Dialog.Apply)
|
let btn = editDialog.standardButton(Basic.Dialog.Apply)
|
||||||
if (!btn)
|
if (!btn)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (editDialog.previousString !== editTextField.text) {
|
btn.enabled = (editDialog.previousString !== editTextField.text)
|
||||||
btn.enabled = true
|
|
||||||
} else {
|
|
||||||
btn.enabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: editDialog.accept()
|
onAccepted: editDialog.accept()
|
||||||
@@ -355,7 +351,7 @@ Rectangle {
|
|||||||
editTextField.text = StatesEditorBackend.statesEditorModel.activeStateGroup
|
editTextField.text = StatesEditorBackend.statesEditorModel.activeStateGroup
|
||||||
editDialog.previousString = StatesEditorBackend.statesEditorModel.activeStateGroup
|
editDialog.previousString = StatesEditorBackend.statesEditorModel.activeStateGroup
|
||||||
|
|
||||||
let btn = editDialog.standardButton(Dialog.Apply)
|
let btn = editDialog.standardButton(Basic.Dialog.Apply)
|
||||||
btn.enabled = false
|
btn.enabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -526,6 +526,10 @@ protected:
|
|||||||
{
|
{
|
||||||
out("pragma ", ast->pragmaToken);
|
out("pragma ", ast->pragmaToken);
|
||||||
out(ast->name.toString());
|
out(ast->name.toString());
|
||||||
|
if (!ast->value.isEmpty()) {
|
||||||
|
out(": ");
|
||||||
|
out(ast->value.toString());
|
||||||
|
}
|
||||||
newLine();
|
newLine();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -36,14 +36,7 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
|||||||
auto endValueIterator = values.end();
|
auto endValueIterator = values.end();
|
||||||
auto lastValueIterator = endValueIterator;
|
auto lastValueIterator = endValueIterator;
|
||||||
|
|
||||||
while (true) {
|
auto doUpdate = [&](const auto &sqliteValue, const auto &value) {
|
||||||
bool hasMoreValues = currentValueIterator != endValueIterator;
|
|
||||||
bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator;
|
|
||||||
if (hasMoreValues && hasMoreSqliteValues) {
|
|
||||||
auto &&sqliteValue = *currentSqliteIterator;
|
|
||||||
auto &&value = *currentValueIterator;
|
|
||||||
auto compare = compareKey(sqliteValue, value);
|
|
||||||
if (compare == 0) {
|
|
||||||
UpdateChange updateChange = updateCallback(sqliteValue, value);
|
UpdateChange updateChange = updateCallback(sqliteValue, value);
|
||||||
switch (updateChange) {
|
switch (updateChange) {
|
||||||
case UpdateChange::Update:
|
case UpdateChange::Update:
|
||||||
@@ -55,10 +48,14 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
|||||||
}
|
}
|
||||||
++currentSqliteIterator;
|
++currentSqliteIterator;
|
||||||
++currentValueIterator;
|
++currentValueIterator;
|
||||||
} else if (compare > 0) {
|
};
|
||||||
|
|
||||||
|
auto doInsert = [&](const auto &value) {
|
||||||
insertCallback(value);
|
insertCallback(value);
|
||||||
++currentValueIterator;
|
++currentValueIterator;
|
||||||
} else if (compare < 0) {
|
};
|
||||||
|
|
||||||
|
auto doDelete = [&](const auto &sqliteValue) {
|
||||||
if (lastValueIterator != endValueIterator) {
|
if (lastValueIterator != endValueIterator) {
|
||||||
if (compareKey(sqliteValue, *lastValueIterator) != 0)
|
if (compareKey(sqliteValue, *lastValueIterator) != 0)
|
||||||
deleteCallback(sqliteValue);
|
deleteCallback(sqliteValue);
|
||||||
@@ -67,20 +64,25 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
|||||||
deleteCallback(sqliteValue);
|
deleteCallback(sqliteValue);
|
||||||
}
|
}
|
||||||
++currentSqliteIterator;
|
++currentSqliteIterator;
|
||||||
}
|
};
|
||||||
} else if (hasMoreValues) {
|
|
||||||
insertCallback(*currentValueIterator);
|
while (true) {
|
||||||
++currentValueIterator;
|
bool hasMoreValues = currentValueIterator != endValueIterator;
|
||||||
} else if (hasMoreSqliteValues) {
|
bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator;
|
||||||
|
if (hasMoreValues && hasMoreSqliteValues) {
|
||||||
auto &&sqliteValue = *currentSqliteIterator;
|
auto &&sqliteValue = *currentSqliteIterator;
|
||||||
if (lastValueIterator != endValueIterator) {
|
auto &&value = *currentValueIterator;
|
||||||
if (compareKey(sqliteValue, *lastValueIterator) != 0)
|
auto compare = compareKey(sqliteValue, value);
|
||||||
deleteCallback(sqliteValue);
|
if (compare == 0)
|
||||||
lastValueIterator = endValueIterator;
|
doUpdate(sqliteValue, value);
|
||||||
} else {
|
else if (compare > 0)
|
||||||
deleteCallback(sqliteValue);
|
doInsert(value);
|
||||||
}
|
else if (compare < 0)
|
||||||
++currentSqliteIterator;
|
doDelete(sqliteValue);
|
||||||
|
} else if (hasMoreValues) {
|
||||||
|
doInsert(*currentValueIterator);
|
||||||
|
} else if (hasMoreSqliteValues) {
|
||||||
|
doDelete(*currentSqliteIterator);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -191,7 +191,7 @@ void SqlStatementBuilder::clearSqlStatement()
|
|||||||
|
|
||||||
void SqlStatementBuilder::checkIfPlaceHolderExists(Utils::SmallStringView name) const
|
void SqlStatementBuilder::checkIfPlaceHolderExists(Utils::SmallStringView name) const
|
||||||
{
|
{
|
||||||
if (name.size() < 2 || !name.startsWith('$') || !m_sqlTemplate.contains(name))
|
if (name.size() < 2 || !name.starts_with('$') || !m_sqlTemplate.contains(name))
|
||||||
throwException("SqlStatementBuilder::bind: placeholder name does not exist!", name.data());
|
throwException("SqlStatementBuilder::bind: placeholder name does not exist!", name.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -112,13 +112,13 @@ public:
|
|||||||
|
|
||||||
~BasicSmallString() noexcept
|
~BasicSmallString() noexcept
|
||||||
{
|
{
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
if (hasAllocatedMemory()) [[unlikely]]
|
||||||
Memory::deallocate(m_data.data());
|
Memory::deallocate(m_data.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicSmallString(const BasicSmallString &other) noexcept
|
BasicSmallString(const BasicSmallString &other) noexcept
|
||||||
{
|
{
|
||||||
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
if (other.isShortString() || other.isReadOnlyReference()) [[likely]]
|
||||||
m_data = other.m_data;
|
m_data = other.m_data;
|
||||||
else
|
else
|
||||||
new (this) BasicSmallString{other.data(), other.size()};
|
new (this) BasicSmallString{other.data(), other.size()};
|
||||||
@@ -126,10 +126,10 @@ public:
|
|||||||
|
|
||||||
BasicSmallString &operator=(const BasicSmallString &other) noexcept
|
BasicSmallString &operator=(const BasicSmallString &other) noexcept
|
||||||
{
|
{
|
||||||
if (Q_LIKELY(this != &other)) {
|
if (this != &other) [[likely]] {
|
||||||
this->~BasicSmallString();
|
this->~BasicSmallString();
|
||||||
|
|
||||||
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
if (other.isShortString() || other.isReadOnlyReference()) [[likely]]
|
||||||
m_data = other.m_data;
|
m_data = other.m_data;
|
||||||
else
|
else
|
||||||
new (this) BasicSmallString{other.data(), other.size()};
|
new (this) BasicSmallString{other.data(), other.size()};
|
||||||
@@ -146,7 +146,7 @@ public:
|
|||||||
|
|
||||||
BasicSmallString &operator=(BasicSmallString &&other) noexcept
|
BasicSmallString &operator=(BasicSmallString &&other) noexcept
|
||||||
{
|
{
|
||||||
if (Q_LIKELY(this != &other)) {
|
if (this != &other) [[likely]] {
|
||||||
this->~BasicSmallString();
|
this->~BasicSmallString();
|
||||||
|
|
||||||
m_data = std::move(other.m_data);
|
m_data = std::move(other.m_data);
|
||||||
@@ -174,7 +174,7 @@ public:
|
|||||||
|
|
||||||
SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); }
|
SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); }
|
||||||
|
|
||||||
operator SmallStringView() const noexcept { return SmallStringView(data(), size()); }
|
constexpr operator SmallStringView() const noexcept { return SmallStringView(data(), size()); }
|
||||||
|
|
||||||
explicit operator QLatin1StringView() const noexcept
|
explicit operator QLatin1StringView() const noexcept
|
||||||
{
|
{
|
||||||
@@ -202,7 +202,7 @@ public:
|
|||||||
void reserve(size_type newCapacity) noexcept
|
void reserve(size_type newCapacity) noexcept
|
||||||
{
|
{
|
||||||
if (fitsNotInCapacity(newCapacity)) {
|
if (fitsNotInCapacity(newCapacity)) {
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory())) {
|
if (hasAllocatedMemory()) {
|
||||||
m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity));
|
m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity));
|
||||||
m_data.setAllocatedCapacity(newCapacity);
|
m_data.setAllocatedCapacity(newCapacity);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -13,12 +13,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#if __cpp_lib_constexpr_string >= 201907L
|
|
||||||
#define constexpr_string constexpr
|
|
||||||
#else
|
|
||||||
#define constexpr_string
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
template <typename String>
|
template <typename String>
|
||||||
@@ -36,41 +30,14 @@ class SmallStringView : public std::string_view
|
|||||||
public:
|
public:
|
||||||
using std::string_view::string_view;
|
using std::string_view::string_view;
|
||||||
|
|
||||||
constexpr SmallStringView(const_iterator begin, const_iterator end) noexcept
|
|
||||||
: std::string_view{std::addressof(*begin), static_cast<std::size_t>(std::distance(begin, end))}
|
|
||||||
{}
|
|
||||||
|
|
||||||
#ifdef Q_CC_MSVC
|
|
||||||
constexpr SmallStringView(const char *const begin, const char *const end) noexcept
|
|
||||||
: std::string_view{begin, static_cast<std::size_t>(std::distance(begin, end))}
|
|
||||||
{}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename String, typename Utils::enable_if_has_char_data_pointer<String> = 0>
|
template<typename String, typename Utils::enable_if_has_char_data_pointer<String> = 0>
|
||||||
constexpr SmallStringView(const String &string) noexcept
|
constexpr SmallStringView(const String &string) noexcept
|
||||||
: std::string_view{string.data(), static_cast<std::size_t>(string.size())}
|
: std::string_view{string.data(), static_cast<std::size_t>(string.size())}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
static constexpr SmallStringView fromUtf8(const char *const characterPointer)
|
|
||||||
{
|
|
||||||
return SmallStringView(characterPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_type isEmpty() const noexcept { return empty(); }
|
constexpr size_type isEmpty() const noexcept { return empty(); }
|
||||||
|
|
||||||
constexpr
|
operator std::string() const { return std::string(data(), size()); }
|
||||||
SmallStringView mid(size_type position) const noexcept
|
|
||||||
{
|
|
||||||
return SmallStringView(data() + position, size() - position);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr
|
|
||||||
SmallStringView mid(size_type position, size_type length) const noexcept
|
|
||||||
{
|
|
||||||
return SmallStringView(data() + position, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr_string operator std::string() const { return std::string(data(), size()); }
|
|
||||||
|
|
||||||
explicit operator QString() const { return QString::fromUtf8(data(), int(size())); }
|
explicit operator QString() const { return QString::fromUtf8(data(), int(size())); }
|
||||||
|
|
||||||
@@ -89,57 +56,18 @@ public:
|
|||||||
{
|
{
|
||||||
return QUtf8StringView(data(), Utils::ssize(*this));
|
return QUtf8StringView(data(), Utils::ssize(*this));
|
||||||
}
|
}
|
||||||
constexpr bool startsWith(SmallStringView subStringToSearch) const noexcept
|
|
||||||
{
|
|
||||||
if (size() >= subStringToSearch.size())
|
|
||||||
return !std::char_traits<char>::compare(data(),
|
|
||||||
subStringToSearch.data(),
|
|
||||||
subStringToSearch.size());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool startsWith(char characterToSearch) const noexcept
|
|
||||||
{
|
|
||||||
return *begin() == characterToSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool endsWith(SmallStringView ending) const noexcept
|
|
||||||
{
|
|
||||||
return size() >= ending.size() && std::equal(ending.rbegin(), ending.rend(), rbegin());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr bool operator!=(SmallStringView first, SmallStringView second) noexcept
|
inline constexpr auto operator<=>(const SmallStringView &first, const SmallStringView &second)
|
||||||
{
|
{
|
||||||
return std::string_view{first} != std::string_view{second};
|
return std::string_view{first} <=> std::string_view{second};
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(SmallStringView first, SmallStringView second) noexcept
|
inline constexpr bool operator==(const SmallStringView &first, const SmallStringView &second)
|
||||||
{
|
{
|
||||||
return std::string_view{first} == std::string_view{second};
|
return std::string_view{first} == std::string_view{second};
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator<(SmallStringView first, SmallStringView second) noexcept
|
|
||||||
{
|
|
||||||
return std::string_view{first} < std::string_view{second};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool operator>(SmallStringView first, SmallStringView second) noexcept
|
|
||||||
{
|
|
||||||
return std::string_view{first} > std::string_view{second};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool operator<=(SmallStringView first, SmallStringView second) noexcept
|
|
||||||
{
|
|
||||||
return std::string_view{first} <= std::string_view{second};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool operator>=(SmallStringView first, SmallStringView second) noexcept
|
|
||||||
{
|
|
||||||
return std::string_view{first} >= std::string_view{second};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr int compare(SmallStringView first, SmallStringView second) noexcept
|
constexpr int compare(SmallStringView first, SmallStringView second) noexcept
|
||||||
{
|
{
|
||||||
return first.compare(second);
|
return first.compare(second);
|
||||||
|
@@ -103,7 +103,6 @@ void InsightWidget::reloadQmlSource()
|
|||||||
{
|
{
|
||||||
QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml");
|
QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml");
|
||||||
QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return );
|
QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return );
|
||||||
engine()->clearComponentCache();
|
|
||||||
setSource(QUrl::fromLocalFile(statesListQmlFilePath));
|
setSource(QUrl::fromLocalFile(statesListQmlFilePath));
|
||||||
|
|
||||||
if (!rootObject()) {
|
if (!rootObject()) {
|
||||||
|
@@ -100,6 +100,11 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I
|
|||||||
cmdLine.addArg(directory);
|
cmdLine.addArg(directory);
|
||||||
return cmdLine;
|
return cmdLine;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(this, &BuildStep::addOutput, this, [](const QString &str, OutputFormat fmt) {
|
||||||
|
if (fmt == OutputFormat::ErrorMessage)
|
||||||
|
showError(str);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for QDS-13763, when UL-10456 is completed this can be removed with the next LTS
|
// Workaround for QDS-13763, when UL-10456 is completed this can be removed with the next LTS
|
||||||
|
@@ -104,7 +104,6 @@ add_qtc_plugin(QmlDesigner
|
|||||||
qmldesignericons.h
|
qmldesignericons.h
|
||||||
qmldesignerplugin.cpp qmldesignerplugin.h
|
qmldesignerplugin.cpp qmldesignerplugin.h
|
||||||
qmldesignerexternaldependencies.cpp qmldesignerexternaldependencies.h
|
qmldesignerexternaldependencies.cpp qmldesignerexternaldependencies.h
|
||||||
qmldesignerprojectmanager.cpp qmldesignerprojectmanager.h
|
|
||||||
settingspage.cpp settingspage.h
|
settingspage.cpp settingspage.h
|
||||||
shortcutmanager.cpp shortcutmanager.h
|
shortcutmanager.cpp shortcutmanager.h
|
||||||
designermcumanager.cpp designermcumanager.h
|
designermcumanager.cpp designermcumanager.h
|
||||||
@@ -133,6 +132,16 @@ if (QTC_STATIC_BUILD AND TARGET QmlDesigner)
|
|||||||
extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor)
|
extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
extend_qtc_plugin(QmlDesigner
|
||||||
|
PUBLIC_INCLUDES project
|
||||||
|
SOURCES_PREFIX project
|
||||||
|
SOURCES
|
||||||
|
qmldesignerprojectmanager.cpp
|
||||||
|
qmldesignerprojectmanager.h
|
||||||
|
projectstorageerrornotifier.cpp
|
||||||
|
projectstorageerrornotifier.h
|
||||||
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
PUBLIC_INCLUDES instances
|
PUBLIC_INCLUDES instances
|
||||||
SOURCES_PREFIX instances
|
SOURCES_PREFIX instances
|
||||||
@@ -412,7 +421,9 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h
|
gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h
|
||||||
gradientpresetitem.cpp gradientpresetitem.h
|
gradientpresetitem.cpp gradientpresetitem.h
|
||||||
gradientpresetlistmodel.cpp gradientpresetlistmodel.h
|
gradientpresetlistmodel.cpp gradientpresetlistmodel.h
|
||||||
|
instanceimageprovider.cpp instanceimageprovider.h
|
||||||
propertyeditorcontextobject.cpp propertyeditorcontextobject.h
|
propertyeditorcontextobject.cpp propertyeditorcontextobject.h
|
||||||
|
propertyeditordynamicpropertiesproxymodel.cpp propertyeditordynamicpropertiesproxymodel.h
|
||||||
propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h
|
propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h
|
||||||
propertyeditortransaction.cpp propertyeditortransaction.h
|
propertyeditortransaction.cpp propertyeditortransaction.h
|
||||||
propertyeditorvalue.cpp propertyeditorvalue.h
|
propertyeditorvalue.cpp propertyeditorvalue.h
|
||||||
@@ -421,7 +432,9 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
propertynamevalidator.cpp propertynamevalidator.h
|
propertynamevalidator.cpp propertynamevalidator.h
|
||||||
tooltip.cpp tooltip.h
|
tooltip.cpp tooltip.h
|
||||||
qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h
|
qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h
|
||||||
|
qmlmaterialnodeproxy.cpp qmlmaterialnodeproxy.h
|
||||||
qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h
|
qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h
|
||||||
|
qmltexturenodeproxy.cpp qmltexturenodeproxy.h
|
||||||
quick2propertyeditorview.cpp quick2propertyeditorview.h
|
quick2propertyeditorview.cpp quick2propertyeditorview.h
|
||||||
propertyeditorutils.cpp propertyeditorutils.h
|
propertyeditorutils.cpp propertyeditorutils.h
|
||||||
)
|
)
|
||||||
@@ -477,7 +490,6 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
materialbrowserwidget.cpp materialbrowserwidget.h
|
materialbrowserwidget.cpp materialbrowserwidget.h
|
||||||
materialbrowsermodel.cpp materialbrowsermodel.h
|
materialbrowsermodel.cpp materialbrowsermodel.h
|
||||||
materialbrowsertexturesmodel.cpp materialbrowsertexturesmodel.h
|
materialbrowsertexturesmodel.cpp materialbrowsertexturesmodel.h
|
||||||
materialutils.cpp materialutils.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
@@ -571,6 +583,16 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
annotationeditor.qrc
|
annotationeditor.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
extend_qtc_plugin(QmlDesigner
|
||||||
|
SOURCES_PREFIX components/scripteditor
|
||||||
|
SOURCES
|
||||||
|
scripteditorstatements.cpp scripteditorstatements.h
|
||||||
|
scripteditorevaluator.cpp scripteditorevaluator.h
|
||||||
|
scripteditorutils.cpp scripteditorutils.h
|
||||||
|
propertytreemodel.cpp propertytreemodel.h
|
||||||
|
scripteditorbackend.cpp scripteditorbackend.h
|
||||||
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
SOURCES_PREFIX components/connectioneditor
|
SOURCES_PREFIX components/connectioneditor
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -578,15 +600,12 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
bindingmodel.cpp bindingmodel.h
|
bindingmodel.cpp bindingmodel.h
|
||||||
bindingmodelitem.cpp bindingmodelitem.h
|
bindingmodelitem.cpp bindingmodelitem.h
|
||||||
connectioneditor.qrc
|
connectioneditor.qrc
|
||||||
connectioneditorevaluator.cpp connectioneditorevaluator.h
|
|
||||||
connectioneditorstatements.cpp connectioneditorstatements.h
|
|
||||||
connectionmodel.cpp connectionmodel.h
|
connectionmodel.cpp connectionmodel.h
|
||||||
connectionview.cpp connectionview.h
|
connectionview.cpp connectionview.h
|
||||||
dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h
|
dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h
|
||||||
dynamicpropertiesitem.cpp dynamicpropertiesitem.h
|
dynamicpropertiesitem.cpp dynamicpropertiesitem.h
|
||||||
connectioneditorutils.cpp connectioneditorutils.h
|
|
||||||
selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h
|
selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h
|
||||||
propertytreemodel.cpp propertytreemodel.h
|
connectioneditorlogging.cpp connectioneditorlogging.h
|
||||||
)
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
@@ -780,39 +799,6 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
dvconnector.cpp dvconnector.h
|
dvconnector.cpp dvconnector.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_qtc_plugin(assetexporterplugin
|
|
||||||
PLUGIN_CLASS AssetExporterPlugin
|
|
||||||
CONDITION TARGET QmlDesigner
|
|
||||||
PLUGIN_DEPENDS
|
|
||||||
Core ProjectExplorer QmlDesigner
|
|
||||||
DEPENDS Utils Qt::Qml Qt::QuickPrivate
|
|
||||||
PUBLIC_INCLUDES assetexporterplugin
|
|
||||||
PLUGIN_PATH ${QmlDesignerPluginInstallPrefix}
|
|
||||||
)
|
|
||||||
|
|
||||||
extend_qtc_plugin(assetexporterplugin
|
|
||||||
CONDITION ENABLE_COMPILE_WARNING_AS_ERROR
|
|
||||||
PROPERTIES COMPILE_WARNING_AS_ERROR ON
|
|
||||||
)
|
|
||||||
|
|
||||||
extend_qtc_plugin(assetexporterplugin
|
|
||||||
SOURCES_PREFIX assetexporterplugin
|
|
||||||
SOURCES
|
|
||||||
assetexportdialog.h assetexportdialog.cpp
|
|
||||||
assetexporter.h assetexporter.cpp
|
|
||||||
assetexporterplugin.h assetexporterplugin.cpp
|
|
||||||
assetexporterview.h assetexporterview.cpp
|
|
||||||
assetexportpluginconstants.h
|
|
||||||
componentexporter.h componentexporter.cpp
|
|
||||||
exportnotification.h exportnotification.cpp
|
|
||||||
filepathmodel.h filepathmodel.cpp
|
|
||||||
dumpers/assetnodedumper.h dumpers/assetnodedumper.cpp
|
|
||||||
dumpers/itemnodedumper.h dumpers/itemnodedumper.cpp
|
|
||||||
dumpers/nodedumper.h dumpers/nodedumper.cpp
|
|
||||||
dumpers/textnodedumper.h dumpers/textnodedumper.cpp
|
|
||||||
assetexporterplugin.qrc
|
|
||||||
)
|
|
||||||
|
|
||||||
add_qtc_plugin(componentsplugin
|
add_qtc_plugin(componentsplugin
|
||||||
PLUGIN_CLASS ComponentsPlugin
|
PLUGIN_CLASS ComponentsPlugin
|
||||||
CONDITION TARGET QmlDesigner
|
CONDITION TARGET QmlDesigner
|
||||||
|
@@ -1,228 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "assetexportdialog.h"
|
|
||||||
|
|
||||||
#include "../qmldesignertr.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
#include "filepathmodel.h"
|
|
||||||
|
|
||||||
#include <coreplugin/fileutils.h>
|
|
||||||
#include <coreplugin/icore.h>
|
|
||||||
|
|
||||||
#include <projectexplorer/task.h>
|
|
||||||
#include <projectexplorer/taskhub.h>
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
|
|
||||||
#include <utils/detailswidget.h>
|
|
||||||
#include <utils/layoutbuilder.h>
|
|
||||||
#include <utils/outputformatter.h>
|
|
||||||
#include <utils/pathchooser.h>
|
|
||||||
|
|
||||||
#include <QCheckBox>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QListView>
|
|
||||||
#include <QPlainTextEdit>
|
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QScrollBar>
|
|
||||||
#include <QGridLayout>
|
|
||||||
#include <QProgressBar>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QStackedWidget>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
using namespace Utils;
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
static void addFormattedMessage(OutputFormatter *formatter, const QString &str, OutputFormat format)
|
|
||||||
{
|
|
||||||
if (!formatter)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QPlainTextEdit *edit = formatter->plainTextEdit();
|
|
||||||
QScrollBar *scroll = edit->verticalScrollBar();
|
|
||||||
bool isAtBottom = scroll && scroll->value() == scroll->maximum();
|
|
||||||
|
|
||||||
QString msg = str + "\n";
|
|
||||||
formatter->appendMessage(msg, format);
|
|
||||||
|
|
||||||
if (isAtBottom)
|
|
||||||
scroll->setValue(scroll->maximum());
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetExportDialog::AssetExportDialog(const FilePath &exportPath,
|
|
||||||
AssetExporter &assetExporter, FilePathModel &model,
|
|
||||||
QWidget *parent) :
|
|
||||||
QDialog(parent),
|
|
||||||
m_assetExporter(assetExporter),
|
|
||||||
m_filePathModel(model),
|
|
||||||
m_filesView(new QListView),
|
|
||||||
m_exportLogs(new QPlainTextEdit),
|
|
||||||
m_outputFormatter(new Utils::OutputFormatter())
|
|
||||||
{
|
|
||||||
resize(768, 480);
|
|
||||||
setWindowTitle(Tr::tr("Export Components"));
|
|
||||||
|
|
||||||
m_stackedWidget = new QStackedWidget;
|
|
||||||
|
|
||||||
m_exportProgress = new QProgressBar;
|
|
||||||
m_exportProgress->setRange(0,0);
|
|
||||||
|
|
||||||
auto optionsWidget = new QWidget;
|
|
||||||
|
|
||||||
auto advancedOptions = new DetailsWidget;
|
|
||||||
advancedOptions->setSummaryText(tr("Advanced Options"));
|
|
||||||
advancedOptions->setWidget(optionsWidget);
|
|
||||||
|
|
||||||
m_buttonBox = new QDialogButtonBox;
|
|
||||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close);
|
|
||||||
|
|
||||||
m_exportPath = new PathChooser;
|
|
||||||
m_exportPath->setExpectedKind(PathChooser::Kind::SaveFile);
|
|
||||||
m_exportPath->setFilePath(
|
|
||||||
exportPath.pathAppended(
|
|
||||||
ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata"
|
|
||||||
));
|
|
||||||
m_exportPath->setPromptDialogTitle(tr("Choose Export File"));
|
|
||||||
m_exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)"));
|
|
||||||
m_exportPath->lineEdit()->setReadOnly(true);
|
|
||||||
m_exportPath->addButton(tr("Open"), this, [this] {
|
|
||||||
Core::FileUtils::showInGraphicalShell(m_exportPath->filePath());
|
|
||||||
});
|
|
||||||
|
|
||||||
m_exportAssetsCheck = new QCheckBox(tr("Export assets"), this);
|
|
||||||
m_exportAssetsCheck->setChecked(true);
|
|
||||||
|
|
||||||
m_perComponentExportCheck = new QCheckBox(tr("Export components separately"), this);
|
|
||||||
m_perComponentExportCheck->setChecked(false);
|
|
||||||
|
|
||||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
|
||||||
|
|
||||||
m_stackedWidget->addWidget(m_filesView);
|
|
||||||
m_filesView->setModel(&m_filePathModel);
|
|
||||||
|
|
||||||
m_exportLogs->setReadOnly(true);
|
|
||||||
m_outputFormatter->setPlainTextEdit(m_exportLogs);
|
|
||||||
m_stackedWidget->addWidget(m_exportLogs);
|
|
||||||
switchView(false);
|
|
||||||
|
|
||||||
connect(m_buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this] {
|
|
||||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
|
||||||
m_assetExporter.cancel();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_exportBtn = m_buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
|
|
||||||
m_exportBtn->setEnabled(false);
|
|
||||||
connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport);
|
|
||||||
connect(&m_filePathModel, &FilePathModel::modelReset, this, [this] {
|
|
||||||
m_exportProgress->setRange(0, 1000);
|
|
||||||
m_exportProgress->setValue(0);
|
|
||||||
m_exportBtn->setEnabled(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this] {
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
|
|
||||||
|
|
||||||
connect(&m_assetExporter, &AssetExporter::stateChanged,
|
|
||||||
this, &AssetExportDialog::onExportStateChanged);
|
|
||||||
connect(&m_assetExporter, &AssetExporter::exportProgressChanged,
|
|
||||||
this, &AssetExportDialog::updateExportProgress);
|
|
||||||
|
|
||||||
connect(&taskHub(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded);
|
|
||||||
|
|
||||||
using namespace Layouting;
|
|
||||||
|
|
||||||
Column {
|
|
||||||
m_exportAssetsCheck,
|
|
||||||
m_perComponentExportCheck,
|
|
||||||
st,
|
|
||||||
noMargin,
|
|
||||||
}.attachTo(optionsWidget);
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Form { Tr::tr("Export path:"), m_exportPath },
|
|
||||||
advancedOptions,
|
|
||||||
m_stackedWidget,
|
|
||||||
m_exportProgress,
|
|
||||||
m_buttonBox,
|
|
||||||
}.attachTo(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetExportDialog::~AssetExportDialog()
|
|
||||||
{
|
|
||||||
m_assetExporter.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExportDialog::onExport()
|
|
||||||
{
|
|
||||||
switchView(true);
|
|
||||||
|
|
||||||
updateExportProgress(0.0);
|
|
||||||
TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT);
|
|
||||||
m_exportLogs->clear();
|
|
||||||
|
|
||||||
Utils::FilePath selectedPath = m_exportPath->filePath();
|
|
||||||
Utils::FilePath exportPath = m_perComponentExportCheck->isChecked() ?
|
|
||||||
(selectedPath.isDir() ? selectedPath : selectedPath.parentDir()) :
|
|
||||||
selectedPath;
|
|
||||||
|
|
||||||
m_assetExporter.exportQml(m_filePathModel.files(), exportPath,
|
|
||||||
m_exportAssetsCheck->isChecked(),
|
|
||||||
m_perComponentExportCheck->isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState)
|
|
||||||
{
|
|
||||||
switch (newState) {
|
|
||||||
case AssetExporter::ParsingState::ExportingDone:
|
|
||||||
m_exportBtn->setVisible(false);
|
|
||||||
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(true);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone);
|
|
||||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExportDialog::updateExportProgress(double value)
|
|
||||||
{
|
|
||||||
value = std::max(0.0, std::min(1.0, value));
|
|
||||||
m_exportProgress->setValue(std::round(value * 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExportDialog::switchView(bool showExportView)
|
|
||||||
{
|
|
||||||
if (showExportView)
|
|
||||||
m_stackedWidget->setCurrentWidget(m_exportLogs);
|
|
||||||
else
|
|
||||||
m_stackedWidget->setCurrentWidget(m_filesView);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task)
|
|
||||||
{
|
|
||||||
Utils::OutputFormat format = Utils::NormalMessageFormat;
|
|
||||||
if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) {
|
|
||||||
switch (task.type) {
|
|
||||||
case ProjectExplorer::Task::Error:
|
|
||||||
format = Utils::StdErrFormat;
|
|
||||||
break;
|
|
||||||
case ProjectExplorer::Task::Warning:
|
|
||||||
format = Utils::StdOutFormat;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
format = Utils::NormalMessageFormat;
|
|
||||||
}
|
|
||||||
addFormattedMessage(m_outputFormatter, task.description(), format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "assetexporter.h"
|
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QStringListModel>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QPushButton;
|
|
||||||
class QCheckBox;
|
|
||||||
class QDialogButtonBox;
|
|
||||||
class QListView;
|
|
||||||
class QPlainTextEdit;
|
|
||||||
class QProgressBar;
|
|
||||||
class QStackedWidget;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace Utils {
|
|
||||||
class OutputFormatter;
|
|
||||||
class PathChooser;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
|
||||||
class Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
class FilePathModel;
|
|
||||||
|
|
||||||
class AssetExportDialog : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter,
|
|
||||||
FilePathModel& model, QWidget *parent = nullptr);
|
|
||||||
~AssetExportDialog();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void onExport();
|
|
||||||
void onExportStateChanged(AssetExporter::ParsingState newState);
|
|
||||||
void updateExportProgress(double value);
|
|
||||||
void switchView(bool showExportView);
|
|
||||||
void onTaskAdded(const ProjectExplorer::Task &task);
|
|
||||||
|
|
||||||
private:
|
|
||||||
AssetExporter &m_assetExporter;
|
|
||||||
FilePathModel &m_filePathModel;
|
|
||||||
QPushButton *m_exportBtn = nullptr;
|
|
||||||
QCheckBox *m_exportAssetsCheck = nullptr;
|
|
||||||
QCheckBox *m_perComponentExportCheck = nullptr;
|
|
||||||
QListView *m_filesView = nullptr;
|
|
||||||
QPlainTextEdit *m_exportLogs = nullptr;
|
|
||||||
Utils::OutputFormatter *m_outputFormatter = nullptr;
|
|
||||||
Utils::PathChooser *m_exportPath = nullptr;
|
|
||||||
QDialogButtonBox *m_buttonBox = nullptr;
|
|
||||||
QStackedWidget *m_stackedWidget = nullptr;
|
|
||||||
QProgressBar *m_exportProgress = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // QmlDesigner
|
|
@@ -1,550 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "assetexporter.h"
|
|
||||||
#include "componentexporter.h"
|
|
||||||
#include "exportnotification.h"
|
|
||||||
|
|
||||||
#include <designdocument.h>
|
|
||||||
#include <modelutils.h>
|
|
||||||
#include <nodemetainfo.h>
|
|
||||||
#include <qmldesignerplugin.h>
|
|
||||||
#include <qmldesignertr.h>
|
|
||||||
#include <qmlitemnode.h>
|
|
||||||
#include <qmlobjectnode.h>
|
|
||||||
#include <rewriterview.h>
|
|
||||||
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
|
||||||
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
|
|
||||||
#include <utils/async.h>
|
|
||||||
#include <utils/fileutils.h>
|
|
||||||
#include <utils/qtcassert.h>
|
|
||||||
|
|
||||||
#include <auxiliarydataproperties.h>
|
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QPlainTextEdit>
|
|
||||||
#include <QWaitCondition>
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
using namespace std;
|
|
||||||
namespace {
|
|
||||||
bool makeParentPath(const Utils::FilePath &path)
|
|
||||||
{
|
|
||||||
QDir d;
|
|
||||||
return d.mkpath(path.toFileInfo().absolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray generateHash(const QString &token) {
|
|
||||||
static uint counter = 0;
|
|
||||||
std::mt19937 gen(std::random_device().operator()());
|
|
||||||
std::uniform_int_distribution<> distribution(1, 99999);
|
|
||||||
QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1();
|
|
||||||
return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg)
|
|
||||||
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg)
|
|
||||||
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
class AssetDumper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AssetDumper();
|
|
||||||
~AssetDumper();
|
|
||||||
|
|
||||||
void dumpAsset(const QPixmap &p, const Utils::FilePath &path);
|
|
||||||
|
|
||||||
/* Keeps on dumping until all assets are dumped, then quits */
|
|
||||||
void quitDumper();
|
|
||||||
|
|
||||||
/* Aborts dumping */
|
|
||||||
void abortDumper();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void addAsset(const QPixmap &p, const Utils::FilePath &path);
|
|
||||||
void doDumping(QPromise<void> &promise);
|
|
||||||
void savePixmap(const QPixmap &p, Utils::FilePath &path) const;
|
|
||||||
|
|
||||||
QFuture<void> m_dumpFuture;
|
|
||||||
QMutex m_queueMutex;
|
|
||||||
QWaitCondition m_queueCondition;
|
|
||||||
std::queue<std::pair<QPixmap, Utils::FilePath>> m_assets;
|
|
||||||
std::atomic<bool> m_quitDumper;
|
|
||||||
};
|
|
||||||
|
|
||||||
AssetExporter::AssetExporter(AssetExporterView *view,
|
|
||||||
ProjectExplorer::Project *project,
|
|
||||||
ProjectStorageDependencies projectStorageDependencies)
|
|
||||||
: m_currentState(*this)
|
|
||||||
, m_project(project)
|
|
||||||
, m_view(view)
|
|
||||||
, m_projectStorageDependencies{projectStorageDependencies}
|
|
||||||
{
|
|
||||||
connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded);
|
|
||||||
connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetExporter::~AssetExporter()
|
|
||||||
{
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
|
|
||||||
bool exportAssets, bool perComponentExport)
|
|
||||||
{
|
|
||||||
m_perComponentExport = perComponentExport;
|
|
||||||
ExportNotification::addInfo(tr("Export root directory: %1.\nExporting assets: %2")
|
|
||||||
.arg(exportPath.isDir()
|
|
||||||
? exportPath.toUserOutput()
|
|
||||||
: exportPath.parentDir().toUserOutput())
|
|
||||||
.arg(exportAssets? tr("Yes") : tr("No")));
|
|
||||||
|
|
||||||
if (m_perComponentExport)
|
|
||||||
ExportNotification::addInfo(tr("Each component is exported separately."));
|
|
||||||
|
|
||||||
notifyProgress(0.0);
|
|
||||||
m_exportFiles = qmlFiles;
|
|
||||||
m_totalFileCount = m_exportFiles.count();
|
|
||||||
m_components.clear();
|
|
||||||
m_componentUuidCache.clear();
|
|
||||||
m_exportPath = exportPath.isDir() ? exportPath : exportPath.parentDir();
|
|
||||||
m_exportFile = exportPath.fileName();
|
|
||||||
m_currentState.change(ParsingState::Parsing);
|
|
||||||
if (exportAssets)
|
|
||||||
m_assetDumper = make_unique<AssetDumper>();
|
|
||||||
else
|
|
||||||
m_assetDumper.reset();
|
|
||||||
|
|
||||||
QTimer::singleShot(0, this, &AssetExporter::beginExport);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::beginExport()
|
|
||||||
{
|
|
||||||
for (const Utils::FilePath &p : std::as_const(m_exportFiles)) {
|
|
||||||
if (m_cancelled)
|
|
||||||
break;
|
|
||||||
preprocessQmlFile(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_cancelled)
|
|
||||||
triggerLoadNextFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::cancel()
|
|
||||||
{
|
|
||||||
if (!m_cancelled) {
|
|
||||||
ExportNotification::addInfo(tr("Canceling export."));
|
|
||||||
m_assetDumper.reset();
|
|
||||||
m_cancelled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetExporter::isBusy() const
|
|
||||||
{
|
|
||||||
return m_currentState == AssetExporter::ParsingState::Parsing ||
|
|
||||||
m_currentState == AssetExporter::ParsingState::ExportingAssets ||
|
|
||||||
m_currentState == AssetExporter::ParsingState::WritingJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QPixmap &AssetExporter::generateAsset(const ModelNode &node)
|
|
||||||
{
|
|
||||||
static QPixmap nullPixmap;
|
|
||||||
if (m_cancelled)
|
|
||||||
return nullPixmap;
|
|
||||||
|
|
||||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
|
||||||
QTC_ASSERT(!uuid.isEmpty(), return nullPixmap);
|
|
||||||
|
|
||||||
if (!m_assets.contains(uuid)) {
|
|
||||||
// Generate asset.
|
|
||||||
QmlObjectNode objectNode(node);
|
|
||||||
QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap();
|
|
||||||
m_assets[uuid] = asset;
|
|
||||||
}
|
|
||||||
return m_assets[uuid];
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component,
|
|
||||||
const QString &suffix) const
|
|
||||||
{
|
|
||||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
|
||||||
if (!component || uuid.isEmpty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
const Utils::FilePath assetExportDir =
|
|
||||||
m_perComponentExport ? componentExportDir(component) : m_exportPath;
|
|
||||||
const Utils::FilePath assetPath = assetExportDir.pathAppended("assets")
|
|
||||||
.pathAppended(uuid + suffix + ".png");
|
|
||||||
|
|
||||||
return assetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path)
|
|
||||||
{
|
|
||||||
if (m_cancelled || !m_assetDumper)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_assetDumper->dumpAsset(asset, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::exportComponent(const ModelNode &rootNode)
|
|
||||||
{
|
|
||||||
qCDebug(loggerInfo) << "Exporting component" << rootNode.id();
|
|
||||||
m_components.push_back(make_unique<Component>(*this, rootNode));
|
|
||||||
m_components.back()->exportComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::notifyLoadError(AssetExporterView::LoadState state)
|
|
||||||
{
|
|
||||||
QString errorStr = tr("Unknown error.");
|
|
||||||
switch (state) {
|
|
||||||
case AssetExporterView::LoadState::Exausted:
|
|
||||||
errorStr = tr("Loading file is taking too long.");
|
|
||||||
break;
|
|
||||||
case AssetExporterView::LoadState::QmlErrorState:
|
|
||||||
errorStr = tr("Cannot parse. The file contains coding errors.");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(loggerError) << "QML load error:" << errorStr;
|
|
||||||
ExportNotification::addError(tr("Loading components failed. %1").arg(errorStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::notifyProgress(double value) const
|
|
||||||
{
|
|
||||||
emit exportProgressChanged(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::onQmlFileLoaded()
|
|
||||||
{
|
|
||||||
QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return);
|
|
||||||
qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl();
|
|
||||||
|
|
||||||
QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance()
|
|
||||||
->documentManager()
|
|
||||||
.currentDesignDocument();
|
|
||||||
if (designDocument->hasQmlParseErrors()) {
|
|
||||||
ExportNotification::addError(tr("Cannot export component. Document \"%1\" has parsing errors.")
|
|
||||||
.arg(designDocument->displayName()));
|
|
||||||
} else {
|
|
||||||
exportComponent(m_view->rootModelNode());
|
|
||||||
if (Utils::Result res = m_view->saveQmlFile(); !res) {
|
|
||||||
ExportNotification::addError(tr("Error saving component file. %1")
|
|
||||||
.arg(res.error().isEmpty()? tr("Unknown") : res.error()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyProgress((m_totalFileCount - m_exportFiles.count()) * 0.8 / m_totalFileCount);
|
|
||||||
triggerLoadNextFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::FilePath AssetExporter::componentExportDir(const Component *component) const
|
|
||||||
{
|
|
||||||
return m_exportPath.pathAppended(component->name());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::preprocessQmlFile(const Utils::FilePath &path)
|
|
||||||
{
|
|
||||||
// Load the QML file and assign UUIDs to items having none.
|
|
||||||
// Meanwhile cache the Component UUIDs as well
|
|
||||||
#ifdef QDS_USE_PROJECTSTORAGE
|
|
||||||
ModelPointer model = Model::create(m_projectStorageDependencies,
|
|
||||||
"Item",
|
|
||||||
{Import::createLibraryImport("QtQuick")},
|
|
||||||
path.path());
|
|
||||||
#else
|
|
||||||
ModelPointer model = Model::create("Item", 2, 7);
|
|
||||||
#endif
|
|
||||||
Utils::FileReader reader;
|
|
||||||
if (!reader.fetch(path)) {
|
|
||||||
ExportNotification::addError(tr("Cannot preprocess file: %1. Error %2")
|
|
||||||
.arg(path.toUserOutput()).arg(reader.errorString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPlainTextEdit textEdit;
|
|
||||||
textEdit.setPlainText(QString::fromUtf8(reader.data()));
|
|
||||||
NotIndentingTextEditModifier *modifier = new NotIndentingTextEditModifier(textEdit.document());
|
|
||||||
modifier->setParent(model.get());
|
|
||||||
auto rewriterView = std::make_unique<RewriterView>(m_view->externalDependencies(),
|
|
||||||
QmlDesigner::RewriterView::Validate);
|
|
||||||
rewriterView->setCheckSemanticErrors(false);
|
|
||||||
rewriterView->setTextModifier(modifier);
|
|
||||||
model->attachView(rewriterView.get());
|
|
||||||
rewriterView->restoreAuxiliaryData();
|
|
||||||
ModelNode rootNode = rewriterView->rootModelNode();
|
|
||||||
if (!rootNode.isValid()) {
|
|
||||||
ExportNotification::addError(tr("Cannot preprocess file: %1").arg(path.toUrlishString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assignUuids(rootNode)) {
|
|
||||||
// Some UUIDs were assigned. Rewrite the file.
|
|
||||||
rewriterView->writeAuxiliaryData();
|
|
||||||
const QByteArray data = textEdit.toPlainText().toUtf8();
|
|
||||||
Utils::FileSaver saver(path, QIODevice::Text);
|
|
||||||
saver.write(data);
|
|
||||||
if (!saver.finalize()) {
|
|
||||||
ExportNotification::addError(tr("Cannot update %1.\n%2")
|
|
||||||
.arg(path.toUserOutput()).arg(saver.errorString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the document if already open.
|
|
||||||
// UUIDS are changed and editor must reopen the document, otherwise stale state of the
|
|
||||||
// document is loaded.
|
|
||||||
for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) {
|
|
||||||
if (doc->filePath() == path) {
|
|
||||||
Core::EditorManager::closeDocuments({doc}, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache component UUID
|
|
||||||
const QString uuid = rootNode.auxiliaryDataWithDefault(uuidProperty).toString();
|
|
||||||
m_componentUuidCache[path.toUrlishString()] = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetExporter::assignUuids(const ModelNode &root)
|
|
||||||
{
|
|
||||||
// Assign an UUID to the node without one.
|
|
||||||
// Return true if an assignment takes place.
|
|
||||||
bool changed = false;
|
|
||||||
for (const ModelNode &node : root.allSubModelNodesAndThisNode()) {
|
|
||||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
|
||||||
if (uuid.isEmpty()) {
|
|
||||||
// Assign an unique identifier to the node.
|
|
||||||
QByteArray uuid = generateUuid(node);
|
|
||||||
node.setAuxiliaryData(uuidProperty, QString::fromLatin1(uuid));
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray AssetExporter::generateUuid(const ModelNode &node)
|
|
||||||
{
|
|
||||||
QByteArray uuid;
|
|
||||||
do {
|
|
||||||
uuid = generateHash(node.id());
|
|
||||||
} while (m_usedHashes.contains(uuid));
|
|
||||||
m_usedHashes.insert(uuid);
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AssetExporter::componentUuid(const ModelNode &instance) const
|
|
||||||
{
|
|
||||||
// Returns the UUID of the component's root node
|
|
||||||
// Empty string is returned if the node is not an instance of a component within
|
|
||||||
// the project.
|
|
||||||
if (instance) {
|
|
||||||
const QString path = ModelUtils::componentFilePath(instance);
|
|
||||||
return m_componentUuidCache.value(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::triggerLoadNextFile()
|
|
||||||
{
|
|
||||||
QTimer::singleShot(0, this, &AssetExporter::loadNextFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::loadNextFile()
|
|
||||||
{
|
|
||||||
if (m_cancelled || m_exportFiles.isEmpty()) {
|
|
||||||
notifyProgress(0.8);
|
|
||||||
m_currentState.change(ParsingState::ParsingFinished);
|
|
||||||
writeMetadata();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the next pending file.
|
|
||||||
const Utils::FilePath file = m_exportFiles.takeFirst();
|
|
||||||
ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput()));
|
|
||||||
qCDebug(loggerInfo) << "Loading next file" << file;
|
|
||||||
m_view->loadQmlFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::writeMetadata() const
|
|
||||||
{
|
|
||||||
if (m_cancelled) {
|
|
||||||
notifyProgress(1.0);
|
|
||||||
ExportNotification::addInfo(tr("Export canceled."));
|
|
||||||
m_currentState.change(ParsingState::ExportingDone);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auto writeFile = [](const Utils::FilePath &path, const QJsonArray &artboards) {
|
|
||||||
if (!makeParentPath(path)) {
|
|
||||||
ExportNotification::addError(tr("Writing metadata failed. Cannot create file %1").
|
|
||||||
arg(path.toUrlishString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExportNotification::addInfo(tr("Writing metadata to file %1.").arg(path.toUserOutput()));
|
|
||||||
|
|
||||||
QJsonObject jsonRoot; // TODO: Write plugin info to root
|
|
||||||
jsonRoot.insert("artboards", artboards);
|
|
||||||
QJsonDocument doc(jsonRoot);
|
|
||||||
if (doc.isNull() || doc.isEmpty()) {
|
|
||||||
ExportNotification::addError(tr("Empty JSON document."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::FileSaver saver(path, QIODevice::Text);
|
|
||||||
saver.write(doc.toJson(QJsonDocument::Indented));
|
|
||||||
if (!saver.finalize()) {
|
|
||||||
ExportNotification::addError(tr("Writing metadata failed. %1").
|
|
||||||
arg(saver.errorString()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
m_currentState.change(ParsingState::WritingJson);
|
|
||||||
|
|
||||||
auto const startupProject = ProjectExplorer::ProjectManager::startupProject();
|
|
||||||
QTC_ASSERT(startupProject, return);
|
|
||||||
const QString projectName = startupProject->displayName();
|
|
||||||
|
|
||||||
if (m_perComponentExport) {
|
|
||||||
for (auto &component : m_components) {
|
|
||||||
const Utils::FilePath path = componentExportDir(component.get());
|
|
||||||
writeFile(path.pathAppended(component->name() + ".metadata"), {component->json()});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QJsonArray artboards;
|
|
||||||
std::transform(m_components.cbegin(), m_components.cend(), back_inserter(artboards),
|
|
||||||
[](const unique_ptr<Component> &c) {return c->json(); });
|
|
||||||
writeFile(m_exportPath.pathAppended(m_exportFile), artboards);
|
|
||||||
}
|
|
||||||
notifyProgress(1.0);
|
|
||||||
ExportNotification::addInfo(tr("Export finished."));
|
|
||||||
if (m_assetDumper)
|
|
||||||
m_assetDumper->quitDumper();
|
|
||||||
m_currentState.change(ParsingState::ExportingDone);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetExporter::State::State(AssetExporter &exporter) :
|
|
||||||
m_assetExporter(exporter)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporter::State::change(const ParsingState &state)
|
|
||||||
{
|
|
||||||
qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state;
|
|
||||||
if (m_state != state) {
|
|
||||||
m_state = state;
|
|
||||||
emit m_assetExporter.stateChanged(m_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s)
|
|
||||||
{
|
|
||||||
os << static_cast<std::underlying_type<QmlDesigner::AssetExporter::ParsingState>::type>(s);
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetDumper::AssetDumper():
|
|
||||||
m_quitDumper(false)
|
|
||||||
{
|
|
||||||
m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetDumper::~AssetDumper()
|
|
||||||
{
|
|
||||||
abortDumper();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path)
|
|
||||||
{
|
|
||||||
addAsset(p, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::quitDumper()
|
|
||||||
{
|
|
||||||
m_quitDumper = true;
|
|
||||||
m_queueCondition.wakeAll();
|
|
||||||
if (!m_dumpFuture.isFinished())
|
|
||||||
m_dumpFuture.waitForFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::abortDumper()
|
|
||||||
{
|
|
||||||
if (!m_dumpFuture.isFinished()) {
|
|
||||||
m_dumpFuture.cancel();
|
|
||||||
m_queueCondition.wakeAll();
|
|
||||||
m_dumpFuture.waitForFinished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_queueMutex);
|
|
||||||
qDebug() << "Save Asset:" << path;
|
|
||||||
m_assets.push({p, path});
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::doDumping(QPromise<void> &promise)
|
|
||||||
{
|
|
||||||
auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) {
|
|
||||||
QMutexLocker locker(&m_queueMutex);
|
|
||||||
if (m_assets.empty())
|
|
||||||
return false;
|
|
||||||
*asset = m_assets.front();
|
|
||||||
m_assets.pop();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
forever {
|
|
||||||
std::pair<QPixmap, Utils::FilePath> asset;
|
|
||||||
if (haveAsset(&asset)) {
|
|
||||||
if (promise.isCanceled())
|
|
||||||
break;
|
|
||||||
savePixmap(asset.first, asset.second);
|
|
||||||
} else {
|
|
||||||
if (m_quitDumper)
|
|
||||||
break;
|
|
||||||
QMutexLocker locker(&m_queueMutex);
|
|
||||||
m_queueCondition.wait(&m_queueMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (promise.isCanceled())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const
|
|
||||||
{
|
|
||||||
if (p.isNull()) {
|
|
||||||
qCDebug(loggerWarn) << "Dumping null pixmap" << path;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!makeParentPath(path)) {
|
|
||||||
ExportNotification::addError(Tr::tr("Error creating asset directory. %1").arg(path.fileName()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!p.save(path.toUrlishString())) {
|
|
||||||
ExportNotification::addError(Tr::tr("Error saving asset. %1").arg(path.fileName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,99 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "assetexporterview.h"
|
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QSet>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace ProjectExplorer { class Project; }
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class AssetDumper;
|
|
||||||
class Component;
|
|
||||||
|
|
||||||
class AssetExporter : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum class ParsingState {
|
|
||||||
Idle = 0,
|
|
||||||
Parsing,
|
|
||||||
ParsingFinished,
|
|
||||||
ExportingAssets,
|
|
||||||
ExportingAssetsFinished,
|
|
||||||
WritingJson,
|
|
||||||
ExportingDone
|
|
||||||
};
|
|
||||||
|
|
||||||
AssetExporter(AssetExporterView *view,
|
|
||||||
ProjectExplorer::Project *project,
|
|
||||||
ProjectStorageDependencies projectStorageDependencies);
|
|
||||||
~AssetExporter();
|
|
||||||
|
|
||||||
void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
|
|
||||||
bool exportAssets, bool perComponentExport);
|
|
||||||
|
|
||||||
void cancel();
|
|
||||||
bool isBusy() const;
|
|
||||||
|
|
||||||
const QPixmap &generateAsset(const ModelNode &node);
|
|
||||||
Utils::FilePath assetPath(const ModelNode &node, const Component *component,
|
|
||||||
const QString &suffix = {}) const;
|
|
||||||
void exportAsset(const QPixmap &asset, const Utils::FilePath &path);
|
|
||||||
QByteArray generateUuid(const ModelNode &node);
|
|
||||||
QString componentUuid(const ModelNode &instance) const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void stateChanged(ParsingState);
|
|
||||||
void exportProgressChanged(double) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingState currentState() const { return m_currentState.m_state; }
|
|
||||||
void exportComponent(const ModelNode &rootNode);
|
|
||||||
void writeMetadata() const;
|
|
||||||
void notifyLoadError(AssetExporterView::LoadState state);
|
|
||||||
void notifyProgress(double value) const;
|
|
||||||
void triggerLoadNextFile();
|
|
||||||
void loadNextFile();
|
|
||||||
|
|
||||||
void onQmlFileLoaded();
|
|
||||||
Utils::FilePath componentExportDir(const Component *component) const;
|
|
||||||
|
|
||||||
void beginExport();
|
|
||||||
void preprocessQmlFile(const Utils::FilePath &path);
|
|
||||||
bool assignUuids(const ModelNode &root);
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable class State {
|
|
||||||
public:
|
|
||||||
State(AssetExporter&);
|
|
||||||
void change(const ParsingState &state);
|
|
||||||
operator ParsingState() const { return m_state; }
|
|
||||||
AssetExporter &m_assetExporter;
|
|
||||||
ParsingState m_state = ParsingState::Idle;
|
|
||||||
} m_currentState;
|
|
||||||
ProjectExplorer::Project *m_project = nullptr;
|
|
||||||
AssetExporterView *m_view = nullptr;
|
|
||||||
Utils::FilePaths m_exportFiles;
|
|
||||||
unsigned int m_totalFileCount = 0;
|
|
||||||
Utils::FilePath m_exportPath;
|
|
||||||
QString m_exportFile;
|
|
||||||
bool m_perComponentExport = false;
|
|
||||||
std::vector<std::unique_ptr<Component>> m_components;
|
|
||||||
QHash<QString, QString> m_componentUuidCache;
|
|
||||||
QSet<QByteArray> m_usedHashes;
|
|
||||||
QHash<QString, QPixmap> m_assets;
|
|
||||||
ProjectStorageDependencies m_projectStorageDependencies;
|
|
||||||
std::unique_ptr<AssetDumper> m_assetDumper;
|
|
||||||
bool m_cancelled = false;
|
|
||||||
};
|
|
||||||
QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s);
|
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
|
@@ -1,115 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "assetexporterplugin.h"
|
|
||||||
|
|
||||||
#include "assetexportdialog.h"
|
|
||||||
#include "assetexporter.h"
|
|
||||||
#include "assetexporterview.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
#include "componentexporter.h"
|
|
||||||
#include "filepathmodel.h"
|
|
||||||
#include <qmldesignerprojectmanager.h>
|
|
||||||
|
|
||||||
#include "dumpers/itemnodedumper.h"
|
|
||||||
#include "dumpers/textnodedumper.h"
|
|
||||||
#include "dumpers/assetnodedumper.h"
|
|
||||||
|
|
||||||
#include "coreplugin/actionmanager/actionmanager.h"
|
|
||||||
#include "coreplugin/actionmanager/actioncontainer.h"
|
|
||||||
#include "coreplugin/documentmanager.h"
|
|
||||||
#include "qmldesigner/qmldesignerplugin.h"
|
|
||||||
#include "projectexplorer/projectexplorerconstants.h"
|
|
||||||
#include "projectexplorer/projectmanager.h"
|
|
||||||
#include "projectexplorer/project.h"
|
|
||||||
#include "projectexplorer/projectmanager.h"
|
|
||||||
#include "projectexplorer/taskhub.h"
|
|
||||||
|
|
||||||
#include "extensionsystem/pluginmanager.h"
|
|
||||||
#include "extensionsystem/pluginspec.h"
|
|
||||||
|
|
||||||
#include "utils/algorithm.h"
|
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QAction>
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
AssetExporterPlugin::AssetExporterPlugin()
|
|
||||||
: m_projectManager{QmlDesigner::QmlDesignerPlugin::projectManagerForPluginInitializationOnly()}
|
|
||||||
{
|
|
||||||
ProjectExplorer::TaskHub::addCategory({Constants::TASK_CATEGORY_ASSET_EXPORT,
|
|
||||||
tr("Asset Export"),
|
|
||||||
tr("Issues with exporting assets."),
|
|
||||||
false});
|
|
||||||
|
|
||||||
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
|
|
||||||
|
|
||||||
auto &viewManager = designerPlugin->viewManager();
|
|
||||||
m_view = viewManager.registerView(std::make_unique<AssetExporterView>(
|
|
||||||
designerPlugin->externalDependenciesForPluginInitializationOnly()));
|
|
||||||
|
|
||||||
// Add dumper templates for factory instantiation.
|
|
||||||
Component::addNodeDumper<ItemNodeDumper>();
|
|
||||||
Component::addNodeDumper<TextNodeDumper>();
|
|
||||||
Component::addNodeDumper<AssetNodeDumper>();
|
|
||||||
|
|
||||||
// Instantiate actions created by the plugin.
|
|
||||||
addActions();
|
|
||||||
|
|
||||||
connect(ProjectExplorer::ProjectManager::instance(),
|
|
||||||
&ProjectExplorer::ProjectManager::startupProjectChanged,
|
|
||||||
this, &AssetExporterPlugin::updateActions);
|
|
||||||
|
|
||||||
updateActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AssetExporterPlugin::pluginName() const
|
|
||||||
{
|
|
||||||
return QLatin1String("AssetExporterPlugin");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterPlugin::onExport()
|
|
||||||
{
|
|
||||||
auto startupProject = ProjectExplorer::ProjectManager::startupProject();
|
|
||||||
if (!startupProject)
|
|
||||||
return;
|
|
||||||
|
|
||||||
FilePathModel model(startupProject);
|
|
||||||
auto exportDir = startupProject->projectFilePath().parentDir();
|
|
||||||
if (!exportDir.parentDir().isEmpty())
|
|
||||||
exportDir = exportDir.parentDir();
|
|
||||||
exportDir = exportDir.pathAppended(startupProject->displayName() + "_export");
|
|
||||||
AssetExporter assetExporter(m_view, startupProject, m_projectManager.projectStorageDependencies());
|
|
||||||
AssetExportDialog assetExporterDialog(exportDir, assetExporter, model);
|
|
||||||
assetExporterDialog.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterPlugin::addActions()
|
|
||||||
{
|
|
||||||
auto exportAction = new QAction(tr("Export Components"), this);
|
|
||||||
exportAction->setToolTip(tr("Export components in the current project."));
|
|
||||||
connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport);
|
|
||||||
Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML);
|
|
||||||
|
|
||||||
// Add action to build menu
|
|
||||||
Core::ActionContainer *buildMenu =
|
|
||||||
Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
|
|
||||||
buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterPlugin::updateActions()
|
|
||||||
{
|
|
||||||
auto project = ProjectExplorer::ProjectManager::startupProject();
|
|
||||||
QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action();
|
|
||||||
exportAction->setEnabled(project && !project->needsConfiguration());
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AssetExporterPlugin::metaInfo() const
|
|
||||||
{
|
|
||||||
return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo");
|
|
||||||
}
|
|
||||||
|
|
||||||
} //QmlDesigner
|
|
@@ -1,35 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iwidgetplugin.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class AssetExporter;
|
|
||||||
class AssetExporterView;
|
|
||||||
class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json")
|
|
||||||
|
|
||||||
Q_DISABLE_COPY(AssetExporterPlugin)
|
|
||||||
Q_INTERFACES(QmlDesigner::IWidgetPlugin)
|
|
||||||
|
|
||||||
public:
|
|
||||||
AssetExporterPlugin();
|
|
||||||
|
|
||||||
QString metaInfo() const final;
|
|
||||||
QString pluginName() const final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void onExport();
|
|
||||||
void addActions();
|
|
||||||
void updateActions();
|
|
||||||
|
|
||||||
AssetExporterView *m_view = nullptr;
|
|
||||||
class QmlDesignerProjectManager &m_projectManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
|
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"Vendor" : "The Qt Company Ltd",
|
|
||||||
"Category" : "Qt Quick",
|
|
||||||
"Description" : "Plugin for exporting assets and QML from QmlDesigner",
|
|
||||||
"Url" : "http://www.qt.io"
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
MetaInfo {
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/assetexporterplugin">
|
|
||||||
<file>assetexporterplugin.metainfo</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
@@ -1,142 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#include "assetexporterview.h"
|
|
||||||
|
|
||||||
#include "qmlitemnode.h"
|
|
||||||
#include "rewriterview.h"
|
|
||||||
|
|
||||||
#include "coreplugin/editormanager/editormanager.h"
|
|
||||||
#include "coreplugin/editormanager/ieditor.h"
|
|
||||||
#include "coreplugin/modemanager.h"
|
|
||||||
#include "coreplugin/coreconstants.h"
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg)
|
|
||||||
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg)
|
|
||||||
//Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg)
|
|
||||||
|
|
||||||
const int RetryIntervalMs = 500;
|
|
||||||
const int MinRetry = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
AssetExporterView::AssetExporterView(ExternalDependenciesInterface &externalDependencies)
|
|
||||||
: AbstractView(externalDependencies)
|
|
||||||
, m_timer(this)
|
|
||||||
{
|
|
||||||
m_timer.setInterval(RetryIntervalMs);
|
|
||||||
// We periodically check if file is loaded.
|
|
||||||
connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs)
|
|
||||||
{
|
|
||||||
qCDebug(loggerInfo) << "Load file" << path;
|
|
||||||
if (loadingState() == LoadState::Busy)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
setState(LoadState::Busy);
|
|
||||||
m_retryCount = std::max(MinRetry, static_cast<int>((timeoutSecs * 1000) / RetryIntervalMs));
|
|
||||||
m_currentEditor = Core::EditorManager::openEditor(path, Utils::Id(),
|
|
||||||
Core::EditorManager::DoNotMakeVisible);
|
|
||||||
Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN);
|
|
||||||
Core::ModeManager::setFocusToCurrentMode();
|
|
||||||
m_timer.start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::Result AssetExporterView::saveQmlFile() const
|
|
||||||
{
|
|
||||||
if (!m_currentEditor) {
|
|
||||||
qCDebug(loggerWarn) << "Saving QML file failed. No editor.";
|
|
||||||
return Utils::Result::Error("Saving QML file failed. No editor.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_currentEditor->document()->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::modelAttached(Model *model)
|
|
||||||
{
|
|
||||||
if (model->rewriterView() && !model->rewriterView()->errors().isEmpty())
|
|
||||||
setState(LoadState::QmlErrorState);
|
|
||||||
|
|
||||||
AbstractView::modelAttached(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::
|
|
||||||
instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
|
|
||||||
{
|
|
||||||
if (inErrorState() || loadingState() == LoadState::Loaded)
|
|
||||||
return; // Already reached error or connected state.
|
|
||||||
|
|
||||||
// We expect correct dimensions are available if the rootnode's
|
|
||||||
// information change message is received.
|
|
||||||
const auto nodes = informationChangeHash.keys();
|
|
||||||
bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) {
|
|
||||||
return n.isRootNode();
|
|
||||||
});
|
|
||||||
if (hasRootNode)
|
|
||||||
handleMaybeDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::instancesPreviewImageChanged([[maybe_unused]] const QVector<ModelNode> &nodeList)
|
|
||||||
{
|
|
||||||
emit previewChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetExporterView::inErrorState() const
|
|
||||||
{
|
|
||||||
return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetExporterView::isLoaded() const
|
|
||||||
{
|
|
||||||
return isAttached() && QmlItemNode(rootModelNode()).isValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::setState(AssetExporterView::LoadState state)
|
|
||||||
{
|
|
||||||
if (state != m_state) {
|
|
||||||
m_state = state;
|
|
||||||
qCDebug(loggerInfo) << "Loading state changed" << m_state;
|
|
||||||
if (inErrorState() || m_state == LoadState::Loaded) {
|
|
||||||
m_timer.stop();
|
|
||||||
// TODO: Send the loaded signal with a delay. The assumption that model attached and a
|
|
||||||
// valid root object is enough to declare a QML file is ready is incorrect. A ideal
|
|
||||||
// solution would be that the QML Puppet notifies file ready signal.
|
|
||||||
if (m_state == LoadState::Loaded)
|
|
||||||
QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished);
|
|
||||||
else
|
|
||||||
emit loadingError(m_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::handleMaybeDone()
|
|
||||||
{
|
|
||||||
if (isLoaded())
|
|
||||||
setState(LoadState::Loaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetExporterView::handleTimerTimeout()
|
|
||||||
{
|
|
||||||
if (!inErrorState() && loadingState() != LoadState::Loaded)
|
|
||||||
handleMaybeDone();
|
|
||||||
|
|
||||||
if (--m_retryCount < 0)
|
|
||||||
setState(LoadState::Exausted);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s)
|
|
||||||
{
|
|
||||||
os << static_cast<std::underlying_type<QmlDesigner::AssetExporterView::LoadState>::type>(s);
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
QT_END_NAMESPACE
|
|
@@ -1,62 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "abstractview.h"
|
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
namespace Core { class IEditor; }
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
class AssetExporterView : public AbstractView
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum class LoadState {
|
|
||||||
Idle = 1,
|
|
||||||
Busy,
|
|
||||||
Exausted,
|
|
||||||
QmlErrorState,
|
|
||||||
Loaded
|
|
||||||
};
|
|
||||||
|
|
||||||
AssetExporterView(ExternalDependenciesInterface &externalDependencies);
|
|
||||||
|
|
||||||
bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10);
|
|
||||||
Utils::Result saveQmlFile() const;
|
|
||||||
|
|
||||||
void modelAttached(Model *model) override;
|
|
||||||
void instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash) override;
|
|
||||||
void instancesPreviewImageChanged(const QVector<ModelNode> &nodeList) override;
|
|
||||||
|
|
||||||
LoadState loadingState() const { return m_state; }
|
|
||||||
bool inErrorState() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void loadingFinished();
|
|
||||||
void loadingError(LoadState);
|
|
||||||
void previewChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isLoaded() const;
|
|
||||||
void setState(LoadState state);
|
|
||||||
void handleMaybeDone();
|
|
||||||
void handleTimerTimeout();
|
|
||||||
|
|
||||||
Core::IEditor *m_currentEditor = nullptr;
|
|
||||||
QTimer m_timer;
|
|
||||||
int m_retryCount = 0;
|
|
||||||
LoadState m_state = LoadState::Idle;
|
|
||||||
bool m_waitForPuppet = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s);
|
|
||||||
QT_END_NAMESPACE
|
|
@@ -1,66 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <auxiliarydata.h>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
namespace Constants {
|
|
||||||
|
|
||||||
const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml";
|
|
||||||
|
|
||||||
const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export";
|
|
||||||
|
|
||||||
//***************************************************************************
|
|
||||||
// Metadata tags
|
|
||||||
//***************************************************************************
|
|
||||||
// Plugin info tags
|
|
||||||
const char PluginInfoTag[] = "pluginInfo";
|
|
||||||
const char MetadataVersionTag[] = "metadataVersion";
|
|
||||||
|
|
||||||
const char DocumentInfoTag[] = "documentInfo";
|
|
||||||
const char DocumentNameTag[] = "name";
|
|
||||||
|
|
||||||
// Layer data tags
|
|
||||||
const char ArtboardListTag[] = "artboards";
|
|
||||||
|
|
||||||
const char NameTag[] = "name";
|
|
||||||
|
|
||||||
const char XPosTag[] = "x";
|
|
||||||
const char YPosTag[] = "y";
|
|
||||||
const char WidthTag[] = "width";
|
|
||||||
const char HeightTag[] = "height";
|
|
||||||
|
|
||||||
const char MetadataTag[] = "metadata";
|
|
||||||
const char ChildrenTag[] = "children";
|
|
||||||
const char CustomIdTag[] = "customId";
|
|
||||||
const char QmlIdTag[] = "qmlId";
|
|
||||||
const char ExportTypeTag[] = "exportType";
|
|
||||||
const char ExportTypeComponent[] = "component";
|
|
||||||
const char ExportTypeChild[] = "child";
|
|
||||||
const char QmlPropertiesTag[] = "qmlProperties";
|
|
||||||
const char ImportsTag[] = "extraImports";
|
|
||||||
const char UuidTag[] = "uuid";
|
|
||||||
const char ClipTag[] = "clip";
|
|
||||||
const char AssetDataTag[] = "assetData";
|
|
||||||
const char ReferenceAssetTag[] = "referenceAsset";
|
|
||||||
const char AssetPathTag[] = "assetPath";
|
|
||||||
const char AssetBoundsTag[] = "assetBounds";
|
|
||||||
const char OpacityTag[] = "opacity";
|
|
||||||
const char TypeNameTag[] = "typeName";
|
|
||||||
const char TypeIdTag[] = "typeId";
|
|
||||||
|
|
||||||
const char TextDetailsTag[] = "textDetails";
|
|
||||||
const char FontFamilyTag[] = "fontFamily";
|
|
||||||
const char FontSizeTag[] = "fontSize";
|
|
||||||
const char FontStyleTag[] = "fontStyle";
|
|
||||||
const char LetterSpacingTag[] = "kerning";
|
|
||||||
const char TextColorTag[] = "textColor";
|
|
||||||
const char TextContentTag[] = "contents";
|
|
||||||
const char IsMultilineTag[] = "multiline";
|
|
||||||
const char LineHeightTag[] = "lineHeight";
|
|
||||||
const char HAlignTag[] = "horizontalAlignment";
|
|
||||||
const char VAlignTag[] = "verticalAlignment";
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,158 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#include "componentexporter.h"
|
|
||||||
#include "assetexporter.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
#include "exportnotification.h"
|
|
||||||
#include "dumpers/nodedumper.h"
|
|
||||||
|
|
||||||
#include "model.h"
|
|
||||||
#include "nodeabstractproperty.h"
|
|
||||||
#include "nodemetainfo.h"
|
|
||||||
#include "qmlitemnode.h"
|
|
||||||
#include "rewriterview.h"
|
|
||||||
|
|
||||||
#include "utils/qtcassert.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
using namespace Constants;
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> Component::m_readers;
|
|
||||||
Component::Component(AssetExporter &exporter, const ModelNode &rootNode):
|
|
||||||
m_exporter(exporter),
|
|
||||||
m_rootNode(rootNode)
|
|
||||||
{
|
|
||||||
m_name = m_rootNode.id();
|
|
||||||
if (m_name.isEmpty())
|
|
||||||
m_name = QString::fromUtf8(m_rootNode.type());
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject Component::json() const
|
|
||||||
{
|
|
||||||
return m_json;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetExporter &Component::exporter()
|
|
||||||
{
|
|
||||||
return m_exporter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::exportComponent()
|
|
||||||
{
|
|
||||||
QTC_ASSERT(m_rootNode.isValid(), return);
|
|
||||||
m_json = nodeToJson(m_rootNode);
|
|
||||||
// Change the export type to component
|
|
||||||
QJsonObject metadata = m_json.value(MetadataTag).toObject();
|
|
||||||
metadata.insert(ExportTypeTag, ExportTypeComponent);
|
|
||||||
addReferenceAsset(metadata);
|
|
||||||
m_json.insert(MetadataTag, metadata);
|
|
||||||
addImports();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &Component::name() const
|
|
||||||
{
|
|
||||||
return m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeDumper *Component::createNodeDumper(const ModelNode &node) const
|
|
||||||
{
|
|
||||||
std::unique_ptr<NodeDumper> reader;
|
|
||||||
for (auto &dumperCreator: m_readers) {
|
|
||||||
std::unique_ptr<NodeDumper> r(dumperCreator->instance(node));
|
|
||||||
if (r->isExportable()) {
|
|
||||||
if (reader) {
|
|
||||||
if (reader->priority() < r->priority())
|
|
||||||
reader = std::move(r);
|
|
||||||
} else {
|
|
||||||
reader = std::move(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reader)
|
|
||||||
qCDebug(loggerInfo()) << "No dumper for node" << node;
|
|
||||||
|
|
||||||
return reader.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject Component::nodeToJson(const ModelNode &node)
|
|
||||||
{
|
|
||||||
QJsonObject jsonObject;
|
|
||||||
|
|
||||||
// Don't export States, Connection, Timeline etc nodes.
|
|
||||||
if (!node.metaInfo().isQtQuickItem())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::unique_ptr<NodeDumper> dumper(createNodeDumper(node));
|
|
||||||
if (dumper) {
|
|
||||||
jsonObject = dumper->json(*this);
|
|
||||||
} else {
|
|
||||||
ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.")
|
|
||||||
.arg(node.id()).arg(QString::fromUtf8(node.type())));
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonArray children;
|
|
||||||
for (const ModelNode &childnode : node.directSubModelNodes()) {
|
|
||||||
const QJsonObject childJson = nodeToJson(childnode);
|
|
||||||
if (!childJson.isEmpty())
|
|
||||||
children.append(childJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!children.isEmpty())
|
|
||||||
jsonObject.insert(ChildrenTag, children);
|
|
||||||
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::addReferenceAsset(QJsonObject &metadataObject) const
|
|
||||||
{
|
|
||||||
QPixmap refAsset = m_exporter.generateAsset(m_rootNode);
|
|
||||||
stichChildrendAssets(m_rootNode, refAsset);
|
|
||||||
Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref");
|
|
||||||
m_exporter.exportAsset(refAsset, refAssetPath);
|
|
||||||
QJsonObject assetData;
|
|
||||||
if (metadataObject.contains(AssetDataTag))
|
|
||||||
assetData = metadataObject[AssetDataTag].toObject();
|
|
||||||
assetData.insert(ReferenceAssetTag, refAssetPath.toUrlishString());
|
|
||||||
metadataObject.insert(AssetDataTag, assetData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const
|
|
||||||
{
|
|
||||||
if (!node.hasAnySubModelNodes())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QPainter painter(&parentPixmap);
|
|
||||||
for (const ModelNode &child : node.directSubModelNodes()) {
|
|
||||||
QPixmap childPixmap = m_exporter.generateAsset(child);
|
|
||||||
if (childPixmap.isNull())
|
|
||||||
continue;
|
|
||||||
stichChildrendAssets(child, childPixmap);
|
|
||||||
QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform();
|
|
||||||
painter.setTransform(cTransform);
|
|
||||||
painter.drawPixmap(QPoint(0, 0), childPixmap);
|
|
||||||
}
|
|
||||||
painter.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::addImports()
|
|
||||||
{
|
|
||||||
QJsonArray importsArray;
|
|
||||||
for (const Import &import : m_rootNode.model()->imports())
|
|
||||||
importsArray.append(import.toString());
|
|
||||||
|
|
||||||
if (!importsArray.empty())
|
|
||||||
m_json.insert(Constants::ImportsTag, importsArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
|
@@ -1,78 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QByteArrayList>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "utils/qtcassert.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QJsonArray;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class AssetExporter;
|
|
||||||
class ModelNode;
|
|
||||||
class Component;
|
|
||||||
class NodeDumper;
|
|
||||||
|
|
||||||
namespace Internal {
|
|
||||||
class NodeDumperCreatorBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~NodeDumperCreatorBase() {}
|
|
||||||
protected:
|
|
||||||
virtual NodeDumper *instance(const ModelNode &) const = 0;
|
|
||||||
friend Component;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
class NodeDumperCreator : public NodeDumperCreatorBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NodeDumperCreator() = default;
|
|
||||||
~NodeDumperCreator() = default;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
NodeDumper *instance(const ModelNode &node) const { return new T(node); }
|
|
||||||
};
|
|
||||||
} //Internal
|
|
||||||
|
|
||||||
class Component
|
|
||||||
{
|
|
||||||
Q_DECLARE_TR_FUNCTIONS(Component);
|
|
||||||
|
|
||||||
public:
|
|
||||||
Component(AssetExporter& exporter, const ModelNode &rootNode);
|
|
||||||
|
|
||||||
void exportComponent();
|
|
||||||
const QString &name() const;
|
|
||||||
QJsonObject json() const;
|
|
||||||
|
|
||||||
AssetExporter &exporter();
|
|
||||||
|
|
||||||
template<typename T> static void addNodeDumper()
|
|
||||||
{
|
|
||||||
QTC_ASSERT((std::is_base_of<NodeDumper, T>::value), return);
|
|
||||||
m_readers.push_back(std::make_unique<Internal::NodeDumperCreator<T>>());
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
NodeDumper* createNodeDumper(const ModelNode &node) const;
|
|
||||||
QJsonObject nodeToJson(const ModelNode &node);
|
|
||||||
void addReferenceAsset(QJsonObject &metadataObject) const;
|
|
||||||
void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const;
|
|
||||||
void addImports();
|
|
||||||
|
|
||||||
private:
|
|
||||||
AssetExporter& m_exporter;
|
|
||||||
const ModelNode &m_rootNode;
|
|
||||||
QString m_name;
|
|
||||||
QJsonObject m_json;
|
|
||||||
static std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> m_readers;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "assetnodedumper.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
#include "assetexporter.h"
|
|
||||||
|
|
||||||
#include "qmlitemnode.h"
|
|
||||||
#include "componentexporter.h"
|
|
||||||
|
|
||||||
#include "utils/fileutils.h"
|
|
||||||
|
|
||||||
#include <QPixmap>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
using namespace Constants;
|
|
||||||
|
|
||||||
AssetNodeDumper::AssetNodeDumper(const ModelNode &node)
|
|
||||||
: ItemNodeDumper(node)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetNodeDumper::isExportable() const
|
|
||||||
{
|
|
||||||
auto qtQuickImageMetaInfo = model()->qtQuickImageMetaInfo();
|
|
||||||
auto qtQuickRectangleMetaInfo = model()->qtQuickRectangleMetaInfo();
|
|
||||||
return metaInfo().isBasedOn(qtQuickImageMetaInfo, qtQuickRectangleMetaInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject AssetNodeDumper::json(Component &component) const
|
|
||||||
{
|
|
||||||
QJsonObject jsonObject = ItemNodeDumper::json(component);
|
|
||||||
|
|
||||||
AssetExporter &exporter = component.exporter();
|
|
||||||
const Utils::FilePath assetPath = exporter.assetPath(m_node, &component);
|
|
||||||
exporter.exportAsset(exporter.generateAsset(m_node), assetPath);
|
|
||||||
|
|
||||||
QJsonObject assetData;
|
|
||||||
assetData.insert(AssetPathTag, assetPath.toUrlishString());
|
|
||||||
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
|
|
||||||
metadata.insert(AssetDataTag, assetData);
|
|
||||||
jsonObject.insert(MetadataTag, metadata);
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "itemnodedumper.h"
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class Component;
|
|
||||||
|
|
||||||
class AssetNodeDumper : public ItemNodeDumper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AssetNodeDumper(const ModelNode &node);
|
|
||||||
~AssetNodeDumper() override = default;
|
|
||||||
|
|
||||||
bool isExportable() const override;
|
|
||||||
int priority() const override { return 200; }
|
|
||||||
QJsonObject json(Component &component) const override;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,76 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "itemnodedumper.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
#include "assetexporter.h"
|
|
||||||
#include "componentexporter.h"
|
|
||||||
|
|
||||||
#include "qmlitemnode.h"
|
|
||||||
#include "annotation.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
static QString capitalize(const QString &str)
|
|
||||||
{
|
|
||||||
if (str.isEmpty())
|
|
||||||
return {};
|
|
||||||
QString tmp = str;
|
|
||||||
tmp[0] = QChar(str[0]).toUpper().toLatin1();
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
using namespace Constants;
|
|
||||||
|
|
||||||
ItemNodeDumper::ItemNodeDumper(const ModelNode &node)
|
|
||||||
: NodeDumper(node)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QmlDesigner::ItemNodeDumper::isExportable() const
|
|
||||||
{
|
|
||||||
return metaInfo().isQtQuickItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject QmlDesigner::ItemNodeDumper::json([[maybe_unused]] QmlDesigner::Component &component) const
|
|
||||||
{
|
|
||||||
const QmlObjectNode &qmlObjectNode = objectNode();
|
|
||||||
QJsonObject jsonObject;
|
|
||||||
|
|
||||||
const QString qmlId = qmlObjectNode.id();
|
|
||||||
QString name = m_node.simplifiedTypeName();
|
|
||||||
if (!qmlId.isEmpty())
|
|
||||||
name.append("_" + capitalize(qmlId));
|
|
||||||
|
|
||||||
jsonObject.insert(NameTag, name);
|
|
||||||
|
|
||||||
// Position relative to parent
|
|
||||||
QmlItemNode itemNode = qmlObjectNode.toQmlItemNode();
|
|
||||||
QPointF pos = itemNode.instancePosition();
|
|
||||||
jsonObject.insert(XPosTag, pos.x());
|
|
||||||
jsonObject.insert(YPosTag, pos.y());
|
|
||||||
|
|
||||||
// size
|
|
||||||
QSizeF size = itemNode.instanceSize();
|
|
||||||
jsonObject.insert(WidthTag, size.width());
|
|
||||||
jsonObject.insert(HeightTag, size.height());
|
|
||||||
|
|
||||||
QJsonObject metadata;
|
|
||||||
metadata.insert(QmlIdTag, qmlId);
|
|
||||||
metadata.insert(UuidTag, uuid());
|
|
||||||
metadata.insert(ExportTypeTag, ExportTypeChild);
|
|
||||||
metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type()));
|
|
||||||
|
|
||||||
if (m_node.hasCustomId())
|
|
||||||
metadata.insert(CustomIdTag, m_node.customId());
|
|
||||||
|
|
||||||
QString typeId = component.exporter().componentUuid(m_node);
|
|
||||||
if (!typeId.isEmpty())
|
|
||||||
metadata.insert(TypeIdTag, typeId);
|
|
||||||
|
|
||||||
jsonObject.insert(MetadataTag, metadata);
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "nodedumper.h"
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class ModelNode;
|
|
||||||
class Component;
|
|
||||||
|
|
||||||
class ItemNodeDumper : public NodeDumper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ItemNodeDumper(const ModelNode &node);
|
|
||||||
|
|
||||||
~ItemNodeDumper() override = default;
|
|
||||||
|
|
||||||
int priority() const override { return 100; }
|
|
||||||
bool isExportable() const override;
|
|
||||||
QJsonObject json(Component &component) const override;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#include "nodedumper.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
|
|
||||||
#include <auxiliarydataproperties.h>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
NodeDumper::NodeDumper(const ModelNode &node)
|
|
||||||
: m_node(node)
|
|
||||||
, m_objectNode(node)
|
|
||||||
, m_metaInfo(node.metaInfo())
|
|
||||||
, m_model{node.model()}
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant NodeDumper::propertyValue(const PropertyName &name) const
|
|
||||||
{
|
|
||||||
return m_objectNode.instanceValue(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NodeDumper::uuid() const
|
|
||||||
{
|
|
||||||
return m_node.auxiliaryDataWithDefault(uuidProperty).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,40 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "qmlobjectnode.h"
|
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QByteArrayList>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class Component;
|
|
||||||
class ModelNode;
|
|
||||||
|
|
||||||
class NodeDumper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NodeDumper(const ModelNode &node);
|
|
||||||
|
|
||||||
virtual ~NodeDumper() = default;
|
|
||||||
|
|
||||||
virtual int priority() const = 0;
|
|
||||||
virtual bool isExportable() const = 0;
|
|
||||||
virtual QJsonObject json(Component& component) const = 0;
|
|
||||||
|
|
||||||
const NodeMetaInfo &metaInfo() const { return m_metaInfo; }
|
|
||||||
const QmlObjectNode& objectNode() const { return m_objectNode; }
|
|
||||||
QVariant propertyValue(const PropertyName &name) const;
|
|
||||||
QString uuid() const;
|
|
||||||
|
|
||||||
Model *model() const { return m_model; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const ModelNode &m_node;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QmlObjectNode m_objectNode;
|
|
||||||
NodeMetaInfo m_metaInfo;
|
|
||||||
Model *m_model = nullptr;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,90 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "textnodedumper.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
|
|
||||||
#include <model.h>
|
|
||||||
|
|
||||||
#include <QColor>
|
|
||||||
#include <QFontInfo>
|
|
||||||
#include <QFontMetricsF>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QtMath>
|
|
||||||
|
|
||||||
#include <private/qquicktext_p.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const QHash<QString, QString> AlignMapping{
|
|
||||||
{"AlignRight", "RIGHT"},
|
|
||||||
{"AlignHCenter", "CENTER"},
|
|
||||||
{"AlignJustify", "JUSTIFIED"},
|
|
||||||
{"AlignLeft", "LEFT"},
|
|
||||||
{"AlignTop", "TOP"},
|
|
||||||
{"AlignVCenter", "CENTER"},
|
|
||||||
{"AlignBottom", "BOTTOM"}
|
|
||||||
};
|
|
||||||
|
|
||||||
QString toJsonAlignEnum(QString value) {
|
|
||||||
if (value.isEmpty() || !AlignMapping.contains(value))
|
|
||||||
return {};
|
|
||||||
return AlignMapping[value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
using namespace Constants;
|
|
||||||
|
|
||||||
TextNodeDumper::TextNodeDumper(const ModelNode &node)
|
|
||||||
: ItemNodeDumper(node)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextNodeDumper::isExportable() const
|
|
||||||
{
|
|
||||||
auto qtQuickTextMetaInfo = model()->qtQuickTextMetaInfo();
|
|
||||||
auto qtQuickControlsLabelMetaInfo = model()->qtQuickControlsLabelMetaInfo();
|
|
||||||
return metaInfo().isBasedOn(qtQuickTextMetaInfo, qtQuickControlsLabelMetaInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject TextNodeDumper::json([[maybe_unused]] Component &component) const
|
|
||||||
{
|
|
||||||
QJsonObject jsonObject = ItemNodeDumper::json(component);
|
|
||||||
|
|
||||||
QJsonObject textDetails;
|
|
||||||
textDetails.insert(TextContentTag, propertyValue("text").toString());
|
|
||||||
|
|
||||||
QFont font = propertyValue("font").value<QFont>();
|
|
||||||
QFontInfo fontInfo(font);
|
|
||||||
textDetails.insert(FontFamilyTag, fontInfo.family());
|
|
||||||
textDetails.insert(FontStyleTag, fontInfo.styleName());
|
|
||||||
textDetails.insert(FontSizeTag, fontInfo.pixelSize());
|
|
||||||
textDetails.insert(LetterSpacingTag, font.letterSpacing());
|
|
||||||
|
|
||||||
|
|
||||||
QColor fontColor(propertyValue("font.color").toString());
|
|
||||||
textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb));
|
|
||||||
|
|
||||||
textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString()));
|
|
||||||
textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString()));
|
|
||||||
|
|
||||||
textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0);
|
|
||||||
|
|
||||||
// Calculate line height in pixels
|
|
||||||
QFontMetricsF fm(font);
|
|
||||||
auto lineHeightMode = propertyValue("lineHeightMode").value<QQuickText::LineHeightMode>();
|
|
||||||
double lineHeight = propertyValue("lineHeight").toDouble();
|
|
||||||
qreal lineHeightPx = (lineHeightMode == QQuickText::FixedHeight) ?
|
|
||||||
lineHeight : qCeil(fm.height()) * lineHeight;
|
|
||||||
textDetails.insert(LineHeightTag, lineHeightPx);
|
|
||||||
|
|
||||||
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
|
|
||||||
metadata.insert(TextDetailsTag, textDetails);
|
|
||||||
jsonObject.insert(MetadataTag, metadata);
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "itemnodedumper.h"
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class Component;
|
|
||||||
|
|
||||||
class TextNodeDumper : public ItemNodeDumper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TextNodeDumper(const ModelNode &node);
|
|
||||||
~TextNodeDumper() override = default;
|
|
||||||
|
|
||||||
bool isExportable() const override;
|
|
||||||
int priority() const override { return 200; }
|
|
||||||
QJsonObject json(Component &component) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "exportnotification.h"
|
|
||||||
#include "assetexportpluginconstants.h"
|
|
||||||
|
|
||||||
#include "projectexplorer/taskhub.h"
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
namespace {
|
|
||||||
static void addTask(Task::TaskType type, const QString &desc)
|
|
||||||
{
|
|
||||||
qCDebug(loggerDebug) << desc;
|
|
||||||
Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT);
|
|
||||||
TaskHub::addTask(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
void ExportNotification::addError(const QString &errMsg)
|
|
||||||
{
|
|
||||||
addTask(Task::Error, errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExportNotification::addWarning(const QString &warningMsg)
|
|
||||||
{
|
|
||||||
addTask(Task::Warning, warningMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExportNotification::addInfo(const QString &infoMsg)
|
|
||||||
{
|
|
||||||
addTask(Task::Unknown, infoMsg);
|
|
||||||
}
|
|
||||||
} // QmlDesigner
|
|
@@ -1,16 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class ExportNotification
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void addError(const QString &errMsg);
|
|
||||||
static void addWarning(const QString &warningMsg);
|
|
||||||
static void addInfo(const QString &infoMsg);
|
|
||||||
};
|
|
||||||
} // QmlDesigner
|
|
@@ -1,143 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
#include "filepathmodel.h"
|
|
||||||
|
|
||||||
#include "exportnotification.h"
|
|
||||||
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectnodes.h>
|
|
||||||
|
|
||||||
#include <utils/async.h>
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg)
|
|
||||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg)
|
|
||||||
|
|
||||||
void findQmlFiles(QPromise<Utils::FilePath> &promise, const Project *project)
|
|
||||||
{
|
|
||||||
if (!project || promise.isCanceled())
|
|
||||||
return;
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
project->files([&promise, &index](const Node* node) ->bool {
|
|
||||||
if (promise.isCanceled())
|
|
||||||
return false;
|
|
||||||
Utils::FilePath path = node->filePath();
|
|
||||||
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
|
|
||||||
if (isComponent && node->filePath().endsWith(".ui.qml"))
|
|
||||||
promise.addResult(path, index++);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
|
|
||||||
FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent)
|
|
||||||
: QAbstractListModel(parent),
|
|
||||||
m_project(project)
|
|
||||||
{
|
|
||||||
QTimer::singleShot(0, this, &FilePathModel::processProject);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilePathModel::~FilePathModel()
|
|
||||||
{
|
|
||||||
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
|
|
||||||
!m_preprocessWatcher->isFinished()) {
|
|
||||||
ExportNotification::addInfo(tr("Canceling file preparation."));
|
|
||||||
m_preprocessWatcher->cancel();
|
|
||||||
m_preprocessWatcher->waitForFinished();
|
|
||||||
qCDebug(loggerInfo) << "Canceled file preparation.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
Qt::ItemFlags itemFlags = QAbstractListModel::flags(index);
|
|
||||||
if (index.isValid())
|
|
||||||
itemFlags |= Qt::ItemIsUserCheckable;
|
|
||||||
return itemFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
int FilePathModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
if (!parent.isValid())
|
|
||||||
return m_files.size();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant FilePathModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case Qt::DisplayRole:
|
|
||||||
return m_files[index.row()].toUserOutput();
|
|
||||||
case Qt::CheckStateRole:
|
|
||||||
return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
||||||
{
|
|
||||||
if (!index.isValid() || role != Qt::CheckStateRole)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const Utils::FilePath path = m_files[index.row()];
|
|
||||||
if (value == Qt::Checked)
|
|
||||||
m_skipped.erase(path);
|
|
||||||
else
|
|
||||||
m_skipped.insert(path);
|
|
||||||
|
|
||||||
emit dataChanged(index, index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::FilePaths FilePathModel::files() const
|
|
||||||
{
|
|
||||||
Utils::FilePaths selectedPaths;
|
|
||||||
std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths),
|
|
||||||
[this](const Utils::FilePath &path) {
|
|
||||||
return !m_skipped.count(path);
|
|
||||||
});
|
|
||||||
return selectedPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FilePathModel::processProject()
|
|
||||||
{
|
|
||||||
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
|
|
||||||
!m_preprocessWatcher->isFinished()) {
|
|
||||||
qCDebug(loggerError) << "Previous model load not finished.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
m_preprocessWatcher.reset(new QFutureWatcher<Utils::FilePath>(this));
|
|
||||||
connect(m_preprocessWatcher.get(),
|
|
||||||
&QFutureWatcher<Utils::FilePath>::resultReadyAt,
|
|
||||||
this,
|
|
||||||
[this](int resultIndex) {
|
|
||||||
beginInsertRows(index(0, 0), m_files.size(), m_files.size());
|
|
||||||
m_files.append(m_preprocessWatcher->resultAt(resultIndex));
|
|
||||||
endInsertRows();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
|
|
||||||
this, &FilePathModel::endResetModel);
|
|
||||||
|
|
||||||
QFuture<Utils::FilePath> f = Utils::asyncRun(&findQmlFiles, m_project);
|
|
||||||
m_preprocessWatcher->setFuture(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
// Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QFutureWatcher>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
|
||||||
class Project;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QmlDesigner {
|
|
||||||
class FilePathModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_DECLARE_TR_FUNCTIONS(QmlDesigner::FilePathModel)
|
|
||||||
|
|
||||||
public:
|
|
||||||
FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr);
|
|
||||||
~FilePathModel() override;
|
|
||||||
|
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
|
||||||
|
|
||||||
Utils::FilePaths files() const;
|
|
||||||
private:
|
|
||||||
void processProject();
|
|
||||||
|
|
||||||
ProjectExplorer::Project *m_project = nullptr;
|
|
||||||
std::unique_ptr<QFutureWatcher<Utils::FilePath>> m_preprocessWatcher;
|
|
||||||
std::unordered_set<Utils::FilePath> m_skipped;
|
|
||||||
Utils::FilePaths m_files;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user