QmlDesigner: Add dynamic properties to property editor

Added dynamic properties section to property editor and material
editor for all nodes. It shows all dynamic properties defined
for the node with proper editors for the supported types.
Dynamic properties can be added and removed via property editor
as well.
Material editor shows dynamic properties similarly.

Fixes: QDS-7411
Change-Id: Id195f5ca23d55544cea29eb8996690a7eed1cc57
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Thomas Hartmann
2022-07-22 16:46:48 +02:00
parent 242de4d3fb
commit 4c91ed496a
26 changed files with 1659 additions and 89 deletions

View File

@@ -63,6 +63,10 @@ PropertyEditorPane {
Item { width: 1; height: 10 }
DynamicPropertiesSection {
propertiesModel: MaterialEditorDynamicPropertiesModel {}
}
Loader {
id: specificsTwo
@@ -79,7 +83,11 @@ PropertyEditorPane {
}
}
Item { width: 1; height: 10 }
Item {
width: 1
height: 10
visible: specificsTwo.visible
}
Loader {
id: specificsOne

View File

@@ -37,6 +37,10 @@ PropertyEditorPane {
showState: true
}
DynamicPropertiesSection {
propertiesModel: SelectionDynamicPropertiesModel {}
}
GeometrySection {}
Section {

View File

@@ -40,6 +40,10 @@ PropertyEditorPane {
anchors.left: parent.left
anchors.right: parent.right
DynamicPropertiesSection {
propertiesModel: SelectionDynamicPropertiesModel {}
}
Loader {
id: specificsTwo
anchors.left: parent.left

View File

@@ -66,6 +66,10 @@ SecondColumnLayout {
colorEditor.backendValue.resetValue()
}
function initEditor() {
cePopup.initEditor()
}
Connections {
id: backendConnection
target: colorEditor

View File

@@ -44,6 +44,23 @@ T.Popup {
property bool isInValidState: false
function initEditor() {
if (colorEditor.supportGradient && gradientModel.hasGradient) {
colorEditor.color = gradientLine.currentColor
gradientLine.currentColor = colorEditor.color
hexTextField.text = colorEditor.color
popupHexTextField.text = colorEditor.color
}
cePopup.isInValidState = true
colorEditor.originalColor = colorEditor.color
colorPalette.selectedColor = colorEditor.color
colorPicker.color = colorEditor.color
cePopup.createModel()
cePopup.determineActiveColorMode()
}
function commitGradientColor() {
var hexColor = convertColorToString(colorEditor.color)
cePopup.popupHexTextField.text = hexColor
@@ -475,24 +492,10 @@ T.Popup {
}
}
}
Connections {
target: modelNodeBackend
function onSelectionChanged() {
if (colorEditor.supportGradient && gradientModel.hasGradient) {
colorEditor.color = gradientLine.currentColor
gradientLine.currentColor = colorEditor.color
hexTextField.text = colorEditor.color
popupHexTextField.text = colorEditor.color
}
cePopup.isInValidState = true
colorEditor.originalColor = colorEditor.color
colorPalette.selectedColor = colorEditor.color
colorPicker.color = colorEditor.color
cePopup.createModel()
cePopup.determineActiveColorMode()
cePopup.initEditor()
}
}
}

View File

@@ -0,0 +1,772 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import HelperWidgets 2.0
import QtQuick.Templates 2.15 as T
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
Section {
id: root
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("User Added Properties")
property DynamicPropertiesModel propertiesModel: null
property Component colorEditor: Component {
id: colorEditor
ColorEditor {
id: colorEditorControl
property string propertyType
signal remove
supportGradient: false
spacer.visible: false
Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap }
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: colorEditorControl.remove()
}
ExpandingSpacer {}
}
}
property Component intEditor: Component {
id: intEditor
SecondColumnLayout {
id: layoutInt
property var backendValue
property string propertyType
signal remove
SpinBox {
maximumValue: 9999999
minimumValue: -9999999
backendValue: layoutInt.backendValue
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
Item {
height: 10
implicitWidth: {
return StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutInt.remove()
}
ExpandingSpacer {}
}
}
property Component realEditor: Component {
id: realEditor
SecondColumnLayout {
id: layoutReal
property var backendValue
property string propertyType
signal remove
SpinBox {
backendValue: layoutReal.backendValue
minimumValue: -9999999
maximumValue: 9999999
decimals: 2
stepSize: 0.1
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
Item {
height: 10
implicitWidth: {
return StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutReal.remove()
}
ExpandingSpacer {}
}
}
property Component stringEditor: Component {
id: stringEditor
SecondColumnLayout {
id: layoutString
property var backendValue
property string propertyType
signal remove
LineEdit {
backendValue: layoutString.backendValue
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutString.remove()
}
ExpandingSpacer {}
}
}
property Component boolEditor: Component {
id: boolEditor
SecondColumnLayout {
id: layoutBool
property var backendValue
property string propertyType
signal remove
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
text: layoutBool.backendValue.value
backendValue: layoutBool.backendValue
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
Item {
height: 10
implicitWidth: {
return StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutBool.remove()
}
ExpandingSpacer {}
}
}
property Component urlEditor: Component {
id: urlEditor
SecondColumnLayout {
id: layoutUrl
property var backendValue
property string propertyType
signal remove
UrlChooser {
backendValue: layoutUrl.backendValue
comboBox.implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
spacer.implicitWidth: StudioTheme.Values.controlLabelGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutUrl.remove()
}
ExpandingSpacer {}
}
}
property Component aliasEditor: Component {
id: aliasEditor
SecondColumnLayout {
id: layoutAlias
property var backendValue
property string propertyType
property alias lineEdit: lineEdit
signal remove
function updateLineEditText() {
lineEdit.text = lineEdit.backendValue.expression
}
LineEdit {
id: lineEdit
backendValue: layoutAlias.backendValue
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
writeAsExpression: true
showTranslateCheckBox: false
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutAlias.remove()
}
ExpandingSpacer {}
}
}
property Component textureInputEditor: Component {
id: textureInputEditor
SecondColumnLayout {
id: layoutTextureInput
property var backendValue
property string propertyType
signal remove
ItemFilterComboBox {
typeFilter: "QtQuick3D.TextureInput"
validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ }
backendValue: layoutTextureInput.backendValue
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutTextureInput.remove()
}
ExpandingSpacer {}
}
}
property Component vectorEditor: Component {
id: vectorEditor
ColumnLayout {
id: layoutVector
property var backendValue
property string propertyType
property int vecSize: 0
property var proxyValues: []
property var spinBoxes: [boxX, boxY, boxZ, boxW]
signal remove
onVecSizeChanged: updateProxyValues()
spacing: StudioTheme.Values.sectionRowSpacing
function isValidValue(v) {
return !(v === undefined || isNaN(v))
}
function updateExpression() {
for (let i = 0; i < vecSize; ++i) {
if (!isValidValue(proxyValues[i].value))
return
}
let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value
for (let j=1; j < vecSize; ++j)
expStr += ", " + proxyValues[j].value
expStr += ")"
layoutVector.backendValue.expression = expStr
}
function updateProxyValues() {
if (!backendValue)
return;
const startIndex = backendValue.expression.indexOf('(')
const endIndex = backendValue.expression.indexOf(')')
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex)
return
const numberStr = backendValue.expression.slice(startIndex + 1, endIndex)
const numbers = numberStr.split(",")
if (!Array.isArray(numbers) || numbers.length !== vecSize)
return
let vals = []
for (let i = 0; i < vecSize; ++i) {
vals[i] = parseFloat(numbers[i])
if (!isValidValue(vals[i]))
return
}
for (let j = 0; j < vecSize; ++j)
proxyValues[j].value = vals[j]
}
SecondColumnLayout {
SpinBox {
id: boxX
minimumValue: -9999999
maximumValue: 9999999
decimals: 2
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "X"
tooltip: "X"
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
SpinBox {
id: boxY
minimumValue: -9999999
maximumValue: 9999999
decimals: 2
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "Y"
tooltip: "Y"
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutVector.remove()
}
ExpandingSpacer {}
}
SecondColumnLayout {
visible: vecSize > 2
SpinBox {
id: boxZ
minimumValue: -9999999
maximumValue: 9999999
decimals: 2
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "Z"
tooltip: "Z"
visible: vecSize > 2
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
SpinBox {
id: boxW
minimumValue: -9999999
maximumValue: 9999999
decimals: 2
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
visible: vecSize > 3
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "W"
tooltip: "W"
visible: vecSize > 3
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
Item {
height: 10
implicitWidth: {
return StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
visible: vecSize === 2 // Placeholder for last spinbox
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
ExpandingSpacer {}
}
}
}
property Component readonlyEditor: Component {
id: readonlyEditor
SecondColumnLayout {
id: layoutReadonly
property var backendValue
property string propertyType
signal remove
PropertyLabel {
tooltip: layoutReadonly.propertyType
horizontalAlignment: Text.AlignLeft
leftPadding: StudioTheme.Values.actionIndicatorWidth
text: qsTr("No editor for type: ") + layoutReadonly.propertyType
width: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
}
Spacer {
implicitWidth: StudioTheme.Values.twoControlColumnGap
}
IconIndicator {
Layout.alignment: Qt.AlignLeft
icon: StudioTheme.Constants.closeCross
onClicked: layoutReadonly.remove()
}
ExpandingSpacer {}
}
}
Column {
width: parent.width
spacing: StudioTheme.Values.sectionRowSpacing
Repeater {
id: repeater
model: root.propertiesModel
property bool loadActive: true
onCountChanged: {
repeater.loadActive = false
repeater.loadActive = true
}
SectionLayout {
DynamicPropertyRow {
id: propertyRow
model: root.propertiesModel
row: index
}
PropertyLabel {
text: propertyName
tooltip: propertyType
}
Loader {
id: loader
asynchronous: true
active: repeater.loadActive
width: loader.item ? loader.item.width : 0
height: loader.item ? loader.item.height : 0
sourceComponent: {
if (propertyType == "color")
return colorEditor
if (propertyType == "int")
return intEditor
if (propertyType == "real")
return realEditor
if (propertyType == "string")
return stringEditor
if (propertyType == "bool")
return boolEditor
if (propertyType == "url")
return urlEditor
if (propertyType == "alias")
return aliasEditor
if (propertyType == "variant")
return readonlyEditor
if (propertyType == "TextureInput")
return textureInputEditor
if (propertyType == "vector2d" || propertyType == "vector3d" || propertyType == "vector4d")
return vectorEditor
return readonlyEditor
}
onLoaded: {
loader.item.backendValue = propertyRow.backendValue
loader.item.propertyType = propertyType
if (sourceComponent == vectorEditor) {
let vecSize = 2
if (propertyType == "vector3d")
vecSize = 3
else if (propertyType == "vector4d")
vecSize = 4
propertyRow.clearProxyBackendValues()
for (let i = 0; i < vecSize; ++i) {
var newProxyValue = propertyRow.createProxyBackendValue()
loader.item.proxyValues.push(newProxyValue)
newProxyValue.valueChangedQml.connect(loader.item.updateExpression)
loader.item.spinBoxes[i].backendValue = newProxyValue
}
propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues)
loader.item.vecSize = vecSize
loader.item.updateProxyValues()
} else if (sourceComponent == aliasEditor) {
loader.item.lineEdit.text = propertyRow.backendValue.expression
loader.item.backendValue.expressionChanged.connect(loader.item.updateLineEditText)
} else if (sourceComponent == colorEditor) {
loader.item.initEditor()
}
}
Connections {
target: loader.item
function onRemove() {
propertyRow.remove()
}
}
}
}
}
SectionLayout {
PropertyLabel {
text: ""
tooltip: ""
}
SecondColumnLayout {
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
StudioControls.AbstractButton {
id: plusButton
buttonIcon: StudioTheme.Constants.plus
onClicked: {
cePopup.opened ? cePopup.close() : cePopup.open()
forceActiveFocus()
}
}
ExpandingSpacer {}
}
}
}
property T.Popup popup: T.Popup {
id: cePopup
onOpened: {
cePopup.setPopupY()
cePopup.setMainScrollViewHeight()
}
function setMainScrollViewHeight() {
if (Controller.mainScrollView == null)
return
var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem,
cePopup.x, cePopup.y)
Controller.mainScrollView.temporaryHeight = mappedPos.y + cePopup.height
+ StudioTheme.Values.colorEditorPopupMargin
}
function setPopupY() {
if (Controller.mainScrollView == null)
return
var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem,
plusButton.x, plusButton.y)
cePopup.y = Math.max(-mappedPos.y + StudioTheme.Values.colorEditorPopupMargin,
cePopup.__defaultY)
textField.text = root.propertiesModel.newPropertyName()
}
onClosed: Controller.mainScrollView.temporaryHeight = 0
property real __defaultX: (Controller.mainScrollView.contentItem.width
- StudioTheme.Values.colorEditorPopupWidth * 1.5) / 2
property real __defaultY: - StudioTheme.Values.colorEditorPopupPadding
- (StudioTheme.Values.colorEditorPopupSpacing * 2)
- StudioTheme.Values.defaultControlHeight
- StudioTheme.Values.colorEditorPopupLineHeight
+ plusButton.width * 0.5
x: cePopup.__defaultX
y: cePopup.__defaultY
width: 270
height: 160
property int itemWidth: width / 2
property int labelWidth: itemWidth - 32
padding: StudioTheme.Values.border
margins: 2 // If not defined margin will be -1
closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent
contentItem: Item {
id: content
Column {
spacing: StudioTheme.Values.sectionRowSpacing
RowLayout {
width: cePopup.width - 8
PropertyLabel {
text: "Add New Property"
horizontalAlignment: Text.AlignLeft
leftPadding: 8
width: cePopup.width - closeIndicator.width - 24
}
IconIndicator {
id: closeIndicator
icon: StudioTheme.Constants.colorPopupClose
pixelSize: StudioTheme.Values.myIconFontSize * 1.4
onClicked: cePopup.close()
Layout.alignment: Qt.AlignRight
}
}
RowLayout {
PropertyLabel {
id: textLabel
text: "Name"
width: cePopup.labelWidth
}
StudioControls.TextField {
id: textField
actionIndicator.visible: false
translationIndicatorVisible: false
width: cePopup.itemWidth
rightPadding: 8
}
}
RowLayout {
PropertyLabel {
text: "Type"
width: cePopup.labelWidth
}
StudioControls.ComboBox {
id: comboBox
actionIndicator.visible: false
model: ["int", "real", "color", "string", "bool", "url", "alias",
"TextureInput", "vector2d", "vector3d", "vector4d"]
width: cePopup.itemWidth
}
}
Item {
width: 1
height: StudioTheme.Values.sectionRowSpacing
}
RowLayout {
width: cePopup.width
StudioControls.AbstractButton {
id: acceptButton
buttonIcon: qsTr("Add Property")
iconFont: StudioTheme.Constants.font
width: cePopup.width / 3
onClicked: {
root.propertiesModel.createProperty(textField.text, comboBox.currentText)
cePopup.close()
}
Layout.alignment: Qt.AlignHCenter
}
}
}
}
background: Rectangle {
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeInteraction
border.width: StudioTheme.Values.border
MouseArea {
// This area is to eat clicks so they do not go through the popup
anchors.fill: parent
acceptedButtons: Qt.AllButtons
}
}
enter: Transition {}
exit: Transition {}
}
}

View File

@@ -83,7 +83,7 @@ HelperWidgets.ComboBox {
comboBox.currentIndex = comboBox.find(text)
if (text === "") {
if (text === "" || text === "null") {
comboBox.currentIndex = 0
comboBox.editText = comboBox.defaultItem
} else {

View File

@@ -62,6 +62,10 @@ Item {
}
}
onBackendValueChanged: {
spinBox.enabled = backendValue === undefined ? false : !isBlocked(backendValue.name)
}
StudioControls.RealSpinBox {
id: spinBox

View File

@@ -20,6 +20,7 @@ ComponentSection 2.0 ComponentSection.qml
ControlLabel 2.0 ControlLabel.qml
singleton Controller 2.0 Controller.qml
DoubleSpinBox 2.0 DoubleSpinBox.qml
DynamicPropertiesSection 2.0 DynamicPropertiesSection.qml
EditableListView 2.0 EditableListView.qml
ExpandingSpacer 2.0 ExpandingSpacer.qml
ExpressionTextField 2.0 ExpressionTextField.qml

View File

@@ -301,6 +301,7 @@ extend_qtc_plugin(QmlDesigner
fileresourcesmodel.cpp fileresourcesmodel.h
itemfiltermodel.cpp itemfiltermodel.h
gradientmodel.cpp gradientmodel.h
dynamicpropertiesproxymodel.cpp dynamicpropertiesproxymodel.h
gradientpresetcustomlistmodel.cpp gradientpresetcustomlistmodel.h
gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h
gradientpresetitem.cpp gradientpresetitem.h
@@ -322,6 +323,7 @@ extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/materialeditor
SOURCES
materialeditorcontextobject.cpp materialeditorcontextobject.h
materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h
materialeditorqmlbackend.cpp materialeditorqmlbackend.h
materialeditortransaction.cpp materialeditortransaction.h
materialeditorview.cpp materialeditorview.h
@@ -452,6 +454,7 @@ extend_qtc_plugin(QmlDesigner
connectionviewwidget.cpp connectionviewwidget.h connectionviewwidget.ui
delegates.cpp delegates.h
dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h
selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -50,7 +50,7 @@ ConnectionView::ConnectionView(QObject *parent) : AbstractView(parent),
m_connectionViewWidget(new ConnectionViewWidget()),
m_connectionModel(new ConnectionModel(this)),
m_bindingModel(new BindingModel(this)),
m_dynamicPropertiesModel(new DynamicPropertiesModel(this)),
m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)),
m_backendModel(new BackendModel(this))
{
connectionViewWidget()->setBindingModel(m_bindingModel);
@@ -65,7 +65,7 @@ void ConnectionView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
bindingModel()->selectionChanged(QList<ModelNode>());
dynamicPropertiesModel()->selectionChanged(QList<ModelNode>());
dynamicPropertiesModel()->reset();
connectionModel()->resetModel();
connectionViewWidget()->resetItemViews();
backendModel()->resetModel();
@@ -75,7 +75,7 @@ void ConnectionView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
bindingModel()->selectionChanged(QList<ModelNode>());
dynamicPropertiesModel()->selectionChanged(QList<ModelNode>());
dynamicPropertiesModel()->reset();
connectionModel()->resetModel();
connectionViewWidget()->resetItemViews();
}
@@ -121,7 +121,7 @@ void ConnectionView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &p
bindingModel()->bindingRemoved(property.toBindingProperty());
dynamicPropertiesModel()->bindingRemoved(property.toBindingProperty());
} else if (property.isVariantProperty()) {
//### dynamicPropertiesModel->bindingRemoved(property.toVariantProperty());
dynamicPropertiesModel()->variantRemoved(property.toVariantProperty());
} else if (property.isSignalHandlerProperty()) {
connectionModel()->removeRowFromTable(property.toSignalHandlerProperty());
}
@@ -167,7 +167,7 @@ void ConnectionView::selectedNodesChanged(const QList<ModelNode> & selectedNodeL
const QList<ModelNode> & /*lastSelectedNodeList*/)
{
bindingModel()->selectionChanged(selectedNodeList);
dynamicPropertiesModel()->selectionChanged(selectedNodeList);
dynamicPropertiesModel()->reset();
connectionViewWidget()->bindingTableViewSelectionChanged(QModelIndex(), QModelIndex());
connectionViewWidget()->dynamicPropertiesTableViewSelectionChanged(QModelIndex(), QModelIndex());

View File

@@ -374,8 +374,8 @@ void ConnectionViewWidget::invalidateButtonStatus()
} else if (currentTab() == DynamicPropertiesTab) {
emit setEnabledRemoveButton(ui->dynamicPropertiesView->selectionModel()->hasSelection());
auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model());
emit setEnabledAddButton(dynamicPropertiesModel->connectionView()->model() &&
dynamicPropertiesModel->connectionView()->selectedModelNodes().count() == 1);
emit setEnabledAddButton(dynamicPropertiesModel->view()->model() &&
dynamicPropertiesModel->selectedNodes().count() == 1);
} else if (currentTab() == BackendTab) {
emit setEnabledAddButton(true);
emit setEnabledRemoveButton(ui->backendView->selectionModel()->hasSelection());
@@ -545,9 +545,9 @@ void ConnectionViewWidget::editorForDynamic()
QString newValue = m_dynamicEditor->bindingValue().trimmed();
if (m_dynamicIndex.isValid()) {
if (propertiesModel->connectionView()->isWidgetEnabled()
if (qobject_cast<ConnectionView *>(propertiesModel->view())->isWidgetEnabled()
&& (propertiesModel->rowCount() > m_dynamicIndex.row())) {
propertiesModel->connectionView()->executeInTransaction(
propertiesModel->view()->executeInTransaction(
"ConnectionView::setBinding", [this, propertiesModel, newValue]() {
AbstractProperty abProp = propertiesModel->abstractPropertyForRow(
m_dynamicIndex.row());

View File

@@ -209,11 +209,11 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp
return widget;
}
if (!model->connectionView()) {
if (!model->view()) {
qWarning() << "BindingDelegate::createEditor no connection view";
return widget;
}
model->connectionView()->allModelNodes();
model->view()->allModelNodes();
switch (index.column()) {
case DynamicPropertiesModel::TargetModelNodeRow: {
@@ -239,6 +239,10 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp
dynamicPropertiesComboBox->addItem(QLatin1String("url"));
dynamicPropertiesComboBox->addItem(QLatin1String("color"));
dynamicPropertiesComboBox->addItem(QLatin1String("variant"));
dynamicPropertiesComboBox->addItem(QLatin1String("TextureInput"));
dynamicPropertiesComboBox->addItem(QLatin1String("vector2d"));
dynamicPropertiesComboBox->addItem(QLatin1String("vector3d"));
dynamicPropertiesComboBox->addItem(QLatin1String("vector4d"));
return dynamicPropertiesComboBox;
};
case DynamicPropertiesModel::PropertyValueRow: {

View File

@@ -37,6 +37,7 @@
#include <qmldesignerconstants.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <QMessageBox>
#include <QTimer>
@@ -90,6 +91,16 @@ QVariant convertVariantForTypeName(const QVariant &variant, const QmlDesigner::T
} else {
returnValue = QColor(Qt::black);
}
} else if (typeName == "vector2d") {
returnValue = "Qt.vector2d(0, 0)";
} else if (typeName == "vector3d") {
returnValue = "Qt.vector3d(0, 0, 0)";
} else if (typeName == "vector4d") {
returnValue = "Qt.vector4d(0, 0, 0 ,0)";
} else if (typeName == "TextureInput") {
returnValue = "null";
} else if (typeName == "alias") {
returnValue = "null";
} else if (typeName == "Item") {
returnValue = 0;
}
@@ -119,9 +130,18 @@ QmlDesigner::PropertyName DynamicPropertiesModel::unusedProperty(const QmlDesign
return propertyName;
}
DynamicPropertiesModel::DynamicPropertiesModel(ConnectionView *parent)
bool DynamicPropertiesModel::isValueType(const TypeName &type)
{
// "variant" is considered value type as it is initialized as one.
// This may need to change if we provide any kind of proper editor for it.
static const QSet<TypeName> valueTypes {"int", "real", "color", "string", "bool", "url", "variant"};
return valueTypes.contains(type);
}
DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent)
: QStandardItemModel(parent)
, m_connectionView(parent)
, m_view(parent)
, m_explicitSelection(explicitSelection)
{
connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged);
}
@@ -133,8 +153,9 @@ void DynamicPropertiesModel::resetModel()
setHorizontalHeaderLabels(
QStringList({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")}));
if (connectionView()->isAttached()) {
for (const ModelNode &modelNode : connectionView()->selectedModelNodes())
if (m_view->isAttached()) {
const auto nodes = selectedNodes();
for (const ModelNode &modelNode : nodes)
addModelNode(modelNode);
}
@@ -146,8 +167,8 @@ void DynamicPropertiesModel::resetModel()
//Value copying is optional
BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const PropertyName &name, bool copyValue)
{
if (connectionView()->selectedModelNodes().count() == 1) {
const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
if (selectedNodes().count() == 1) {
const ModelNode modelNode = selectedNodes().constFirst();
if (modelNode.isValid()) {
if (modelNode.hasVariantProperty(name)) {
try {
@@ -181,8 +202,8 @@ BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const Property
//If it's a BindingProperty, then replaces it with empty VariantProperty
void DynamicPropertiesModel::resetProperty(const PropertyName &name)
{
if (connectionView()->selectedModelNodes().count() == 1) {
const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
if (selectedNodes().count() == 1) {
const ModelNode modelNode = selectedNodes().constFirst();
if (modelNode.isValid()) {
if (modelNode.hasProperty(name)) {
try {
@@ -190,11 +211,10 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name)
if (abProp.isVariantProperty()) {
VariantProperty property = abProp.toVariantProperty();
QVariant newValue = convertVariantForTypeName(QVariant("none.none"), property.dynamicTypeName());
QVariant newValue = convertVariantForTypeName({}, property.dynamicTypeName());
property.setDynamicTypeNameAndValue(property.dynamicTypeName(),
newValue);
}
else if (abProp.isBindingProperty()) {
} else if (abProp.isBindingProperty()) {
BindingProperty property = abProp.toBindingProperty();
TypeName oldType = property.dynamicTypeName();
@@ -202,9 +222,8 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name)
modelNode.removeProperty(name);
VariantProperty newProperty = modelNode.variantProperty(name);
QVariant newValue = convertVariantForTypeName(QVariant("none.none"), oldType);
newProperty.setDynamicTypeNameAndValue(oldType,
newValue);
QVariant newValue = convertVariantForTypeName({}, oldType);
newProperty.setDynamicTypeNameAndValue(oldType, newValue);
}
} catch (RewritingException &e) {
@@ -226,8 +245,8 @@ void DynamicPropertiesModel::bindingPropertyChanged(const BindingProperty &bindi
m_handleDataChanged = false;
QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
if (!selectedNodes.contains(bindingProperty.parentModelNode()))
const QList<ModelNode> nodes = selectedNodes();
if (!nodes.contains(bindingProperty.parentModelNode()))
return;
if (!m_lock) {
int rowNumber = findRowForBindingProperty(bindingProperty);
@@ -249,17 +268,16 @@ void DynamicPropertiesModel::variantPropertyChanged(const VariantProperty &varia
m_handleDataChanged = false;
QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
if (!selectedNodes.contains(variantProperty.parentModelNode()))
const QList<ModelNode> nodes = selectedNodes();
if (!nodes.contains(variantProperty.parentModelNode()))
return;
if (!m_lock) {
int rowNumber = findRowForVariantProperty(variantProperty);
if (rowNumber == -1) {
if (rowNumber == -1)
addVariantProperty(variantProperty);
} else {
else
updateVariantProperty(rowNumber);
}
}
m_handleDataChanged = true;
@@ -269,8 +287,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper
{
m_handleDataChanged = false;
QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
if (!selectedNodes.contains(bindingProperty.parentModelNode()))
const QList<ModelNode> nodes = selectedNodes();
if (!nodes.contains(bindingProperty.parentModelNode()))
return;
if (!m_lock) {
int rowNumber = findRowForBindingProperty(bindingProperty);
@@ -280,17 +298,36 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper
m_handleDataChanged = true;
}
void DynamicPropertiesModel::selectionChanged(const QList<ModelNode> &selectedNodes)
void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProperty)
{
m_handleDataChanged = false;
const QList<ModelNode> nodes = selectedNodes();
if (!nodes.contains(variantProperty.parentModelNode()))
return;
if (!m_lock) {
int rowNumber = findRowForVariantProperty(variantProperty);
removeRow(rowNumber);
}
m_handleDataChanged = true;
}
void DynamicPropertiesModel::reset()
{
Q_UNUSED(selectedNodes)
m_handleDataChanged = false;
resetModel();
m_handleDataChanged = true;
}
ConnectionView *DynamicPropertiesModel::connectionView() const
void DynamicPropertiesModel::setSelectedNode(const ModelNode &node)
{
return m_connectionView;
QTC_ASSERT(m_explicitSelection, return);
QTC_ASSERT(node.isValid(), return);
m_selectedNodes.clear();
m_selectedNodes.append(node);
reset();
}
AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) const
@@ -298,10 +335,10 @@ AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) c
const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
if (!connectionView()->isAttached())
if (!m_view->isAttached())
return AbstractProperty();
ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
ModelNode modelNode = m_view->modelNodeForInternalId(internalId);
if (modelNode.isValid())
return modelNode.property(targetPropertyName.toUtf8());
@@ -314,7 +351,7 @@ BindingProperty DynamicPropertiesModel::bindingPropertyForRow(int rowNumber) con
const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
ModelNode modelNode = m_view->modelNodeForInternalId(internalId);
if (modelNode.isValid())
return modelNode.bindingProperty(targetPropertyName.toUtf8());
@@ -327,7 +364,7 @@ VariantProperty DynamicPropertiesModel::variantPropertyForRow(int rowNumber) con
const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
ModelNode modelNode = m_view->modelNodeForInternalId(internalId);
if (modelNode.isValid())
return modelNode.variantProperty(targetPropertyName.toUtf8());
@@ -364,8 +401,8 @@ void DynamicPropertiesModel::addDynamicPropertyForCurrentNode()
{
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED);
if (connectionView()->selectedModelNodes().count() == 1) {
const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
if (selectedNodes().count() == 1) {
const ModelNode modelNode = selectedNodes().constFirst();
if (modelNode.isValid()) {
try {
modelNode.variantProperty(unusedProperty(modelNode)).setDynamicTypeNameAndValue("string", QLatin1String("none.none"));
@@ -420,16 +457,14 @@ QStringList DynamicPropertiesModel::possibleSourceProperties(const BindingProper
void DynamicPropertiesModel::deleteDynamicPropertyByRow(int rowNumber)
{
connectionView()->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() {
m_view->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() {
BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
if (bindingProperty.isValid()) {
bindingProperty.parentModelNode().removeProperty(bindingProperty.name());
}
VariantProperty variantProperty = variantPropertyForRow(rowNumber);
if (variantProperty.isValid()) {
variantProperty.parentModelNode().removeProperty(variantProperty.name());
} else {
VariantProperty variantProperty = variantPropertyForRow(rowNumber);
if (variantProperty.isValid())
variantProperty.parentModelNode().removeProperty(variantProperty.name());
}
});
@@ -455,7 +490,6 @@ void DynamicPropertiesModel::addProperty(const QVariant &propertyValue,
items.append(idItem);
items.append(propertyNameItem);
propertyTypeItem = new QStandardItem(propertyType);
items.append(propertyTypeItem);
@@ -531,7 +565,7 @@ void DynamicPropertiesModel::updateValue(int row)
if (bindingProperty.isBindingProperty()) {
const QString expression = data(index(row, PropertyValueRow)).toString();
RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
try {
bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), expression);
transaction.commit(); //committing in the try block
@@ -547,7 +581,7 @@ void DynamicPropertiesModel::updateValue(int row)
if (variantProperty.isVariantProperty()) {
const QVariant value = data(index(row, PropertyValueRow));
RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
try {
variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value);
transaction.commit(); //committing in the try block
@@ -571,7 +605,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber)
ModelNode targetNode = bindingProperty.parentModelNode();
if (bindingProperty.isBindingProperty()) {
connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){
m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){
const QString expression = bindingProperty.expression();
const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName();
@@ -590,7 +624,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber)
const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName();
ModelNode targetNode = variantProperty.parentModelNode();
connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){
m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){
targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, value);
targetNode.removeProperty(variantProperty.name());
});
@@ -616,7 +650,7 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber)
const PropertyName propertyName = bindingProperty.name();
ModelNode targetNode = bindingProperty.parentModelNode();
connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
targetNode.removeProperty(bindingProperty.name());
targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, expression);
});
@@ -632,12 +666,14 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber)
ModelNode targetNode = variantProperty.parentModelNode();
const PropertyName propertyName = variantProperty.name();
connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
targetNode.removeProperty(variantProperty.name());
if (newType == "alias") { //alias properties have to be bindings
targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, QLatin1String("none.none"));
if (!isValueType(newType)) {
targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(
newType, convertVariantForTypeName({}, newType).toString());
} else {
targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(newType, convertVariantForTypeName(value, newType));
targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(
newType, convertVariantForTypeName(value, newType));
}
});
@@ -656,7 +692,7 @@ ModelNode DynamicPropertiesModel::getNodeByIdOrParent(const QString &id, const M
ModelNode modelNode;
if (id != QLatin1String("parent")) {
modelNode = connectionView()->modelNodeForId(id);
modelNode = m_view->modelNodeForId(id);
} else {
if (targetNode.hasParentProperty()) {
modelNode = targetNode.parentProperty().parentModelNode();
@@ -776,6 +812,24 @@ void DynamicPropertiesModel::handleException()
resetModel();
}
const QList<ModelNode> DynamicPropertiesModel::selectedNodes() const
{
// If selected nodes are explicitly set, return those.
// Otherwise return actual selected nodes of the model.
if (m_explicitSelection)
return m_selectedNodes;
else
return m_view->selectedModelNodes();
}
const ModelNode DynamicPropertiesModel::singleSelectedNode() const
{
if (m_explicitSelection)
return m_selectedNodes.first();
else
return m_view->singleSelectedModelNode();
}
} // namespace Internal
} // namespace QmlDesigner

View File

@@ -33,9 +33,9 @@
namespace QmlDesigner {
namespace Internal {
class AbstractView;
class ConnectionView;
namespace Internal {
class DynamicPropertiesModel : public QStandardItemModel
{
@@ -48,13 +48,17 @@ public:
PropertyTypeRow = 2,
PropertyValueRow = 3
};
DynamicPropertiesModel(ConnectionView *parent = nullptr);
DynamicPropertiesModel(bool explicitSelection, AbstractView *parent);
void bindingPropertyChanged(const BindingProperty &bindingProperty);
void variantPropertyChanged(const VariantProperty &variantProperty);
void bindingRemoved(const BindingProperty &bindingProperty);
void selectionChanged(const QList<ModelNode> &selectedNodes);
void variantRemoved(const VariantProperty &variantProperty);
void reset();
void setSelectedNode(const ModelNode &node);
const QList<ModelNode> selectedNodes() const;
const ModelNode singleSelectedNode() const;
ConnectionView *connectionView() const;
AbstractView *view() const { return m_view; }
AbstractProperty abstractPropertyForRow(int rowNumber) const;
BindingProperty bindingPropertyForRow(int rowNumber) const;
VariantProperty variantPropertyForRow(int rowNumber) const;
@@ -71,6 +75,8 @@ public:
QmlDesigner::PropertyName unusedProperty(const QmlDesigner::ModelNode &modelNode);
static bool isValueType(const TypeName &type);
protected:
void addProperty(const QVariant &propertyValue,
const QString &propertyType,
@@ -97,12 +103,12 @@ private:
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
void handleException();
private:
ConnectionView *m_connectionView;
AbstractView *m_view = nullptr;
bool m_lock = false;
bool m_handleDataChanged = false;
QString m_exceptionError;
QList<ModelNode> m_selectedNodes;
bool m_explicitSelection = false;
};
} // namespace Internal

View File

@@ -0,0 +1,43 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "selectiondynamicpropertiesproxymodel.h"
#include <dynamicpropertiesmodel.h>
#include <connectionview.h>
using namespace QmlDesigner::Internal;
SelectionDynamicPropertiesProxyModel::SelectionDynamicPropertiesProxyModel(QObject *parent)
: DynamicPropertiesProxyModel(parent)
{
initModel(ConnectionView::instance()->dynamicPropertiesModel());
}
void SelectionDynamicPropertiesProxyModel::registerDeclarativeType()
{
DynamicPropertiesProxyModel::registerDeclarativeType();
qmlRegisterType<SelectionDynamicPropertiesProxyModel>("HelperWidgets", 2, 0, "SelectionDynamicPropertiesModel");
}

View File

@@ -0,0 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <dynamicpropertiesproxymodel.h>
class SelectionDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel
{
Q_OBJECT
public:
explicit SelectionDynamicPropertiesProxyModel(QObject *parent = nullptr);
static void registerDeclarativeType();
};

View File

@@ -0,0 +1,44 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialeditordynamicpropertiesproxymodel.h"
#include <dynamicpropertiesmodel.h>
#include <materialeditorview.h>
using namespace QmlDesigner;
MaterialEditorDynamicPropertiesProxyModel::MaterialEditorDynamicPropertiesProxyModel(QObject *parent)
: DynamicPropertiesProxyModel(parent)
{
initModel(MaterialEditorView::instance()->dynamicPropertiesModel());
}
void MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType()
{
DynamicPropertiesProxyModel::registerDeclarativeType();
qmlRegisterType<MaterialEditorDynamicPropertiesProxyModel>("HelperWidgets", 2, 0, "MaterialEditorDynamicPropertiesModel");
}

View File

@@ -0,0 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "dynamicpropertiesproxymodel.h"
class MaterialEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel
{
Q_OBJECT
public:
explicit MaterialEditorDynamicPropertiesProxyModel(QObject *parent = nullptr);
static void registerDeclarativeType();
};

View File

@@ -27,11 +27,13 @@
#include "materialeditorqmlbackend.h"
#include "materialeditorcontextobject.h"
#include "materialeditordynamicpropertiesproxymodel.h"
#include "propertyeditorvalue.h"
#include "materialeditortransaction.h"
#include "assetslibrarywidget.h"
#include <bindingproperty.h>
#include <dynamicpropertiesmodel.h>
#include <metainfo.h>
#include <nodeinstanceview.h>
#include <nodelistproperty.h>
@@ -70,6 +72,7 @@ namespace QmlDesigner {
MaterialEditorView::MaterialEditorView(QWidget *parent)
: AbstractView(parent)
, m_stackedWidget(new QStackedWidget(parent))
, m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this))
{
m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget);
connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml);
@@ -92,6 +95,8 @@ MaterialEditorView::MaterialEditorView(QWidget *parent)
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_stackedWidget->setMinimumWidth(250);
QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_MATERIALEDITOR_TIME);
MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType();
}
MaterialEditorView::~MaterialEditorView()
@@ -314,6 +319,29 @@ void MaterialEditorView::currentTimelineChanged(const ModelNode &)
m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this));
}
Internal::DynamicPropertiesModel *MaterialEditorView::dynamicPropertiesModel() const
{
return m_dynamicPropertiesModel;
}
MaterialEditorView *MaterialEditorView::instance()
{
static MaterialEditorView *s_instance = nullptr;
if (s_instance)
return s_instance;
const auto views = QmlDesignerPlugin::instance()->viewManager().views();
for (auto *view : views) {
MaterialEditorView *myView = qobject_cast<MaterialEditorView *>(view);
if (myView)
s_instance = myView;
}
QTC_ASSERT(s_instance, return nullptr);
return s_instance;
}
void MaterialEditorView::delayedResetView()
{
// TODO: it seems the delayed reset is not needed. Leaving it commented out for now just in case it
@@ -592,6 +620,11 @@ void MaterialEditorView::setupQmlBackend()
m_qmlBackEnd = currentQmlBackend;
if (m_hasMaterialRoot)
m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial);
else
m_dynamicPropertiesModel->reset();
delayedTypeUpdate();
initPreviewData();
@@ -751,6 +784,7 @@ void MaterialEditorView::modelAttached(Model *model)
void MaterialEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
m_dynamicPropertiesModel->reset();
m_qmlBackEnd->materialEditorTransaction()->end();
}
@@ -783,8 +817,9 @@ void MaterialEditorView::variantPropertiesChanged(const QList<VariantProperty> &
bool changed = false;
for (const VariantProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
if (property.isDynamic())
m_dynamicPropertiesModel->variantPropertyChanged(property);
if (m_selectedMaterial.property(property.name()).isBindingProperty())
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
else
@@ -810,6 +845,8 @@ void MaterialEditorView::bindingPropertiesChanged(const QList<BindingProperty> &
m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported());
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
if (property.isDynamic())
m_dynamicPropertiesModel->bindingPropertyChanged(property);
if (QmlObjectNode(m_selectedMaterial).modelNode().property(property.name()).isBindingProperty())
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
else
@@ -831,6 +868,16 @@ void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const Prope
m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedMaterial, name);
}
void MaterialEditorView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList)
{
for (const auto &property : propertyList) {
if (property.isBindingProperty())
m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty());
else if (property.isVariantProperty())
m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty());
}
}
// request render image for the selected material node
void MaterialEditorView::requestPreviewRender()
{
@@ -998,6 +1045,7 @@ void MaterialEditorView::customNotification(const AbstractView *view, const QStr
if (identifier == "selected_material_changed") {
if (!m_hasMaterialRoot) {
m_selectedMaterial = nodeList.first();
m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial);
QTimer::singleShot(0, this, &MaterialEditorView::resetView);
}
} else if (identifier == "apply_to_selected_triggered") {

View File

@@ -44,6 +44,10 @@ namespace QmlDesigner {
class ModelNode;
class MaterialEditorQmlBackend;
namespace Internal {
class DynamicPropertiesModel;
}
class MaterialEditorView : public AbstractView
{
Q_OBJECT
@@ -66,6 +70,7 @@ public:
void variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags propertyChange) override;
void bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags propertyChange) override;
void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override;
void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override;
void resetView();
void currentStateChanged(const ModelNode &node) override;
@@ -90,6 +95,10 @@ public:
void currentTimelineChanged(const ModelNode &node) override;
Internal::DynamicPropertiesModel *dynamicPropertiesModel() const;
static MaterialEditorView *instance();
public slots:
void handleToolBarAction(int action);
void handlePreviewEnvChanged(const QString &envAndValue);
@@ -142,6 +151,7 @@ private:
QPointer<QColorDialog> m_colorDialog;
QPointer<ItemLibraryInfo> m_itemLibraryInfo;
Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,364 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "dynamicpropertiesproxymodel.h"
#include "propertyeditorvalue.h"
#include <dynamicpropertiesmodel.h>
#include <abstractproperty.h>
#include <bindingeditor.h>
#include <variantproperty.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <utils/qtcassert.h>
using namespace QmlDesigner;
static const int propertyNameRole = Qt::UserRole + 1;
static const int propertyTypeRole = Qt::UserRole + 2;
static const int propertyValueRole = Qt::UserRole + 3;
static const int propertyBindingRole = Qt::UserRole + 4;
DynamicPropertiesProxyModel::DynamicPropertiesProxyModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void DynamicPropertiesProxyModel::initModel(QmlDesigner::Internal::DynamicPropertiesModel *model)
{
m_model = model;
connect(m_model, &QAbstractItemModel::modelAboutToBeReset,
this, &QAbstractItemModel::modelAboutToBeReset);
connect(m_model, &QAbstractItemModel::modelReset,
this, &QAbstractItemModel::modelReset);
connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
this, &QAbstractItemModel::rowsAboutToBeRemoved);
connect(m_model, &QAbstractItemModel::rowsRemoved,
this, &QAbstractItemModel::rowsRemoved);
connect(m_model, &QAbstractItemModel::rowsInserted,
this, &QAbstractItemModel::rowsInserted);
connect(m_model, &QAbstractItemModel::dataChanged,
this, [this](const QModelIndex &topLeft, const QModelIndex &, const QList<int> &) {
emit dataChanged(index(topLeft.row(), 0),
index(topLeft.row(), 0),
{ propertyNameRole, propertyTypeRole,
propertyValueRole, propertyBindingRole });
});
}
int DynamicPropertiesProxyModel::rowCount(const QModelIndex &) const
{
return m_model->rowCount();
}
QHash<int, QByteArray> DynamicPropertiesProxyModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{propertyNameRole, "propertyName"},
{propertyTypeRole, "propertyType"},
{propertyValueRole, "propertyValue"},
{propertyBindingRole, "propertyBinding"}};
return roleNames;
}
QVariant DynamicPropertiesProxyModel::data(const QModelIndex &index, int role) const
{
if (index.isValid() && index.row() < rowCount()) {
AbstractProperty property = m_model->abstractPropertyForRow(index.row());
QTC_ASSERT(property.isValid(), return QVariant());
if (role == propertyNameRole) {
return property.name();
} else if (propertyTypeRole) {
return property.dynamicTypeName();
} else if (role == propertyValueRole) {
QmlObjectNode objectNode = property.parentQmlObjectNode();
return objectNode.modelValue(property.name());
} else if (role == propertyBindingRole) {
if (property.isBindingProperty())
return property.toBindingProperty().expression();
return QVariant();
}
qWarning() << Q_FUNC_INFO << "invalid role";
} else {
qWarning() << Q_FUNC_INFO << "invalid index";
}
return QVariant();
}
void DynamicPropertiesProxyModel::registerDeclarativeType()
{
static bool registered = false;
if (!registered)
qmlRegisterType<DynamicPropertiesProxyModel>("HelperWidgets", 2, 0, "DynamicPropertiesModel");
}
QmlDesigner::Internal::DynamicPropertiesModel *DynamicPropertiesProxyModel::dynamicPropertiesModel() const
{
return m_model;
}
QString DynamicPropertiesProxyModel::newPropertyName() const
{
auto propertiesModel = dynamicPropertiesModel();
return QString::fromUtf8(propertiesModel->unusedProperty(
propertiesModel->singleSelectedNode()));
}
void DynamicPropertiesProxyModel::createProperty(const QString &name, const QString &type)
{
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED);
const auto selectedNodes = dynamicPropertiesModel()->selectedNodes();
if (selectedNodes.count() == 1) {
const ModelNode modelNode = selectedNodes.constFirst();
if (modelNode.isValid()) {
try {
if (Internal::DynamicPropertiesModel::isValueType(type.toUtf8())) {
QVariant value;
if (type == "int")
value = 0;
else if (type == "real")
value = 0.0;
else if (type == "color")
value = QColor(255, 255, 255);
else if (type == "string")
value = "";
else if (type == "bool")
value = false;
else if (type == "url")
value = "";
else if (type == "variant")
value = "";
modelNode.variantProperty(name.toUtf8())
.setDynamicTypeNameAndValue(type.toUtf8(), value);
} else {
QString expression;
if (type == "alias")
expression = "null";
else if (type == "TextureInput")
expression = "null";
else if (type == "vector2d")
expression = "Qt.vector2d(0, 0)";
else if (type == "vector3d")
expression = "Qt.vector3d(0, 0, 0)";
else if (type == "vector4d")
expression = "Qt.vector4d(0, 0, 0 ,0)";
modelNode.bindingProperty(name.toUtf8())
.setDynamicTypeNameAndExpression(type.toUtf8(), expression);
}
} catch (Exception &e) {
e.showException();
}
}
} else {
qWarning() << " BindingModel::addBindingForCurrentNode not one node selected";
}
}
DynamicPropertyRow::DynamicPropertyRow(QObject *parent)
{
m_backendValue = new PropertyEditorValue(this);
QObject::connect(m_backendValue,
&PropertyEditorValue::valueChanged,
this,
[this](const QString &, const QVariant &value) { commitValue(value); });
QObject::connect(m_backendValue,
&PropertyEditorValue::expressionChanged,
this,
[this](const QString &) { commitExpression(m_backendValue->expression()); });
}
DynamicPropertyRow::~DynamicPropertyRow()
{
clearProxyBackendValues();
}
void DynamicPropertyRow::registerDeclarativeType()
{
qmlRegisterType<DynamicPropertyRow>("HelperWidgets", 2, 0, "DynamicPropertyRow");
}
void DynamicPropertyRow::setRow(int r)
{
if (m_row == r)
return;
m_row = r;
setupBackendValue();
emit rowChanged();
}
int DynamicPropertyRow::row() const
{
return m_row;
}
void DynamicPropertyRow::setModel(DynamicPropertiesProxyModel *model)
{
if (model == m_model)
return;
if (m_model) {
disconnect(m_model, &QAbstractItemModel::dataChanged,
this, &DynamicPropertyRow::handleDataChanged);
}
m_model = model;
if (m_model) {
connect(m_model, &QAbstractItemModel::dataChanged,
this, &DynamicPropertyRow::handleDataChanged);
if (m_row != -1)
setupBackendValue();
}
emit modelChanged();
}
DynamicPropertiesProxyModel *DynamicPropertyRow::model() const
{
return m_model;
}
PropertyEditorValue *DynamicPropertyRow::backendValue() const
{
return m_backendValue;
}
void DynamicPropertyRow::remove()
{
m_model->dynamicPropertiesModel()->deleteDynamicPropertyByRow(m_row);
}
PropertyEditorValue *DynamicPropertyRow::createProxyBackendValue()
{
PropertyEditorValue *newValue = new PropertyEditorValue(this);
m_proxyBackendValues.append(newValue);
return newValue;
}
void DynamicPropertyRow::clearProxyBackendValues()
{
qDeleteAll(m_proxyBackendValues);
m_proxyBackendValues.clear();
}
void DynamicPropertyRow::setupBackendValue()
{
if (!m_model)
return;
QmlDesigner::AbstractProperty property = m_model->dynamicPropertiesModel()->abstractPropertyForRow(m_row);
if (!property.isValid())
return;
if (m_backendValue->name() != property.name())
m_backendValue->setName(property.name());
ModelNode node = property.parentModelNode();
if (node != m_backendValue->modelNode())
m_backendValue->setModelNode(node);
QVariant modelValue = property.parentQmlObjectNode().modelValue(property.name());
if (modelValue != m_backendValue->value()) {
m_backendValue->setValue({});
m_backendValue->setValue(modelValue);
}
if (property.isBindingProperty()) {
QString expression = property.toBindingProperty().expression();
if (m_backendValue->expression() != expression)
m_backendValue->setExpression(expression);
}
emit m_backendValue->isBoundChanged();
}
void DynamicPropertyRow::commitValue(const QVariant &value)
{
auto propertiesModel = m_model->dynamicPropertiesModel();
VariantProperty variantProperty = propertiesModel->variantPropertyForRow(m_row);
if (!Internal::DynamicPropertiesModel::isValueType(variantProperty.dynamicTypeName()))
return;
auto view = propertiesModel->view();
RewriterTransaction transaction = view->beginRewriterTransaction(
QByteArrayLiteral("DynamicPropertiesModel::commitValue"));
try {
if (variantProperty.value() != value)
variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value);
transaction.commit(); //committing in the try block
} catch (Exception &e) {
e.showException();
}
}
void DynamicPropertyRow::commitExpression(const QString &expression)
{
auto propertiesModel = m_model->dynamicPropertiesModel();
BindingProperty bindingProperty = propertiesModel->bindingPropertyForRow(m_row);
auto view = propertiesModel->view();
RewriterTransaction transaction = view->beginRewriterTransaction(
QByteArrayLiteral("DynamicPropertiesModel::commitExpression"));
try {
QString theExpression = expression;
if (theExpression.isEmpty())
theExpression = "null";
if (bindingProperty.expression() != theExpression) {
bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(),
theExpression);
}
transaction.commit(); //committing in the try block
} catch (Exception &e) {
e.showException();
}
return;
}
void DynamicPropertyRow::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList<int> &)
{
if (topLeft.row() == m_row)
setupBackendValue();
}

View File

@@ -0,0 +1,110 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "propertyeditorvalue.h"
#include <abstractview.h>
#include <qmlitemnode.h>
#include <enumeration.h>
#include <QAbstractListModel>
#include <QColor>
#include <QtQml>
namespace QmlDesigner {
namespace Internal {
class DynamicPropertiesModel;
}
} // namespace QmlDesigner
class DynamicPropertiesProxyModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit DynamicPropertiesProxyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
static void registerDeclarativeType();
QmlDesigner::Internal::DynamicPropertiesModel *dynamicPropertiesModel() const;
Q_INVOKABLE QString newPropertyName() const;
Q_INVOKABLE void createProperty(const QString &name, const QString &type);
protected:
void initModel(QmlDesigner::Internal::DynamicPropertiesModel *model);
private:
QmlDesigner::Internal::DynamicPropertiesModel *m_model = nullptr;
};
class DynamicPropertyRow : public QObject
{
Q_OBJECT
Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged FINAL)
Q_PROPERTY(PropertyEditorValue *backendValue READ backendValue NOTIFY rowChanged FINAL)
Q_PROPERTY(DynamicPropertiesProxyModel *model READ model WRITE setModel NOTIFY modelChanged FINAL)
public:
explicit DynamicPropertyRow(QObject *parent = nullptr);
~DynamicPropertyRow();
static void registerDeclarativeType();
void setRow(int r);
int row() const;
void setModel(DynamicPropertiesProxyModel *model);
DynamicPropertiesProxyModel *model() const;
PropertyEditorValue *backendValue() const;
Q_INVOKABLE void remove();
Q_INVOKABLE PropertyEditorValue *createProxyBackendValue();
Q_INVOKABLE void clearProxyBackendValues();
signals:
void rowChanged();
void modelChanged();
private:
void setupBackendValue();
void commitValue(const QVariant &value);
void commitExpression(const QString &expression);
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList<int> &);
int m_row = -1;
PropertyEditorValue *m_backendValue = nullptr;
DynamicPropertiesProxyModel *m_model = nullptr;
QList<PropertyEditorValue *> m_proxyBackendValues;
};
QML_DECLARE_TYPE(DynamicPropertyRow)

View File

@@ -30,6 +30,7 @@
#include "bindingeditor/actioneditor.h"
#include "bindingeditor/bindingeditor.h"
#include "colorpalettebackend.h"
#include "selectiondynamicpropertiesproxymodel.h"
#include "fileresourcesmodel.h"
#include "gradientmodel.h"
#include "gradientpresetcustomlistmodel.h"
@@ -75,6 +76,8 @@ void Quick2PropertyEditorView::registerQmlTypes()
Tooltip::registerDeclarativeType();
EasingCurveEditor::registerDeclarativeType();
RichTextEditorProxy::registerDeclarativeType();
SelectionDynamicPropertiesProxyModel::registerDeclarativeType();
DynamicPropertyRow::registerDeclarativeType();
const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath();

View File

@@ -321,9 +321,10 @@ void BindingProperty::setDynamicTypeNameAndExpression(const TypeName &typeName,
Internal::InternalProperty::Pointer internalProperty = internalNode()->property(name());
if (internalProperty->isBindingProperty()
&& internalProperty->toBindingProperty()->expression() == expression
&& internalProperty->toBindingProperty()->dynamicTypeName() == typeName)
&& internalProperty->toBindingProperty()->dynamicTypeName() == typeName) {
return;
}
}
if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isBindingProperty())

View File

@@ -691,6 +691,8 @@ Project {
"materialbrowser/materialbrowserwidget.h",
"materialeditor/materialeditorcontextobject.cpp",
"materialeditor/materialeditorcontextobject.h",
"materialeditor/materialeditordynamicpropertiesproxymodel.cpp",
"materialeditor/materialeditordynamicpropertiesproxymodel.h",
"materialeditor/materialeditorqmlbackend.cpp",
"materialeditor/materialeditorqmlbackend.h",
"materialeditor/materialeditortransaction.cpp",
@@ -725,6 +727,8 @@ Project {
"propertyeditor/colorpalettebackend.h",
"propertyeditor/designerpropertymap.cpp",
"propertyeditor/designerpropertymap.h",
"propertyeditor/dynamicpropertiesproxymodel.cpp",
"propertyeditor/dynamicpropertiesproxymodel.h",
"propertyeditor/fileresourcesmodel.cpp",
"propertyeditor/fileresourcesmodel.h",
"propertyeditor/itemfiltermodel.cpp",
@@ -843,6 +847,8 @@ Project {
"connectioneditor/connectionviewwidget.ui",
"connectioneditor/dynamicpropertiesmodel.cpp",
"connectioneditor/dynamicpropertiesmodel.h",
"connectioneditor/selectiondynamicpropertiesproxymodel.cpp",
"connectioneditor/selectiondynamicpropertiesproxymodel.h",
"connectioneditor/stylesheet.css",
"curveeditor/curveeditorview.cpp",
"curveeditor/curveeditorview.h",