forked from qt-creator/qt-creator
QmlDesigner: Fix issues with dynamic vectors
The conversion from Qt.vectorxd was not strict enough and expressions like Qt.vector2d(7 / Constants.width, 7 / Constants.height) were read as [7, 7]. Reflection was not handled properly and the [7,7] was written on selection. Fixes: * Avoid reflection * Improve Qt.vector parsing; Move code to C++ * Handle "real" expression case and show binding indicator * Fix setting an expression. We allow the binding editor/expression indicator only on the first component of the vector Task-number: QDS-12242 Change-Id: I6707bdac860852d96d416adbe911d43fe9128e8a Reviewed-by: Henning Gründl <henning.gruendl@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
@@ -291,53 +291,64 @@ Section {
|
|||||||
property int vecSize: 0
|
property int vecSize: 0
|
||||||
property var proxyValues: []
|
property var proxyValues: []
|
||||||
property var spinBoxes: [boxX, boxY, boxZ, boxW]
|
property var spinBoxes: [boxX, boxY, boxZ, boxW]
|
||||||
|
property bool block: false
|
||||||
|
|
||||||
signal remove
|
signal remove
|
||||||
|
|
||||||
onVecSizeChanged: updateProxyValues()
|
onVecSizeChanged: layoutVector.updateProxyValues()
|
||||||
|
|
||||||
spacing: StudioTheme.Values.sectionRowSpacing / 2
|
spacing: StudioTheme.Values.sectionRowSpacing
|
||||||
|
|
||||||
function isValidValue(v) {
|
function isValidValue(v) {
|
||||||
return !(v === undefined || isNaN(v))
|
return !(v === undefined || isNaN(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateExpression() {
|
function updateExpressionFromExpression() {
|
||||||
|
if (layoutVector.block)
|
||||||
|
return
|
||||||
|
|
||||||
|
layoutVector.backendValue.expression = layoutVector.proxyValues[0].expression
|
||||||
|
// Only the first proxy value has an expression editor enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExpressionFromValue() {
|
||||||
|
if (layoutVector.block)
|
||||||
|
return
|
||||||
|
|
||||||
for (let i = 0; i < vecSize; ++i) {
|
for (let i = 0; i < vecSize; ++i) {
|
||||||
if (!isValidValue(proxyValues[i].value))
|
if (!layoutVector.isValidValue(layoutVector.proxyValues[i].value))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value
|
let expStr = "Qt.vector" + layoutVector.vecSize + "d(" + layoutVector.proxyValues[0].value
|
||||||
for (let j=1; j < vecSize; ++j)
|
for (let j=1; j < layoutVector.vecSize; ++j)
|
||||||
expStr += ", " + proxyValues[j].value
|
expStr += ", " + layoutVector.proxyValues[j].value
|
||||||
expStr += ")"
|
expStr += ")"
|
||||||
|
|
||||||
layoutVector.backendValue.expression = expStr
|
layoutVector.backendValue.expression = expStr
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProxyValues() {
|
function updateProxyValues() {
|
||||||
if (!backendValue)
|
if (!layoutVector.backendValue)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const startIndex = backendValue.expression.indexOf('(')
|
let vals = layoutVector.backendValue.getExpressionAsVector()
|
||||||
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 = []
|
layoutVector.block = true
|
||||||
for (let i = 0; i < vecSize; ++i) {
|
|
||||||
vals[i] = parseFloat(numbers[i])
|
if (layoutVector.vecSize === vals.length) {
|
||||||
if (!isValidValue(vals[i]))
|
for (let j = 0; j < layoutVector.vecSize; ++j) {
|
||||||
return
|
layoutVector.proxyValues[j].setForceBound(false)
|
||||||
|
layoutVector.proxyValues[j].value = vals[j]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let j = 0; j < layoutVector.vecSize; ++j) {
|
||||||
|
layoutVector.proxyValues[j].setForceBound(true) // Required since the backendValue is just proxied
|
||||||
|
layoutVector.proxyValues[j].expression = layoutVector.backendValue.expression
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let j = 0; j < vecSize; ++j)
|
layoutVector.block = false
|
||||||
proxyValues[j].value = vals[j]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SecondColumnLayout {
|
SecondColumnLayout {
|
||||||
@@ -357,15 +368,18 @@ Section {
|
|||||||
tooltip: "X"
|
tooltip: "X"
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
Spacer {
|
||||||
|
implicitWidth: StudioTheme.Values.controlGap
|
||||||
|
+ StudioTheme.Values.actionIndicatorWidth
|
||||||
|
}
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
id: boxY
|
id: boxY
|
||||||
|
actionIndicatorVisible: false
|
||||||
minimumValue: -9999999
|
minimumValue: -9999999
|
||||||
maximumValue: 9999999
|
maximumValue: 9999999
|
||||||
decimals: 2
|
decimals: 2
|
||||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||||
+ StudioTheme.Values.actionIndicatorWidth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||||
@@ -386,14 +400,16 @@ Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SecondColumnLayout {
|
SecondColumnLayout {
|
||||||
visible: vecSize > 2
|
visible: layoutVector.vecSize > 2
|
||||||
|
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
id: boxZ
|
id: boxZ
|
||||||
|
actionIndicatorVisible: false
|
||||||
minimumValue: -9999999
|
minimumValue: -9999999
|
||||||
maximumValue: 9999999
|
maximumValue: 9999999
|
||||||
decimals: 2
|
decimals: 2
|
||||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||||
+ StudioTheme.Values.actionIndicatorWidth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||||
@@ -401,19 +417,22 @@ Section {
|
|||||||
ControlLabel {
|
ControlLabel {
|
||||||
text: "Z"
|
text: "Z"
|
||||||
tooltip: "Z"
|
tooltip: "Z"
|
||||||
visible: vecSize > 2
|
visible: layoutVector.vecSize > 2
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
Spacer {
|
||||||
|
implicitWidth: StudioTheme.Values.controlGap
|
||||||
|
+ StudioTheme.Values.actionIndicatorWidth
|
||||||
|
}
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
id: boxW
|
id: boxW
|
||||||
|
actionIndicatorVisible: false
|
||||||
minimumValue: -9999999
|
minimumValue: -9999999
|
||||||
maximumValue: 9999999
|
maximumValue: 9999999
|
||||||
decimals: 2
|
decimals: 2
|
||||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||||
+ StudioTheme.Values.actionIndicatorWidth
|
visible: layoutVector.vecSize > 3
|
||||||
visible: vecSize > 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||||
@@ -421,7 +440,7 @@ Section {
|
|||||||
ControlLabel {
|
ControlLabel {
|
||||||
text: "W"
|
text: "W"
|
||||||
tooltip: "W"
|
tooltip: "W"
|
||||||
visible: vecSize > 3
|
visible: layoutVector.vecSize > 3
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
||||||
@@ -430,7 +449,7 @@ Section {
|
|||||||
height: 10
|
height: 10
|
||||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||||
+ StudioTheme.Values.actionIndicatorWidth
|
+ StudioTheme.Values.actionIndicatorWidth
|
||||||
visible: vecSize === 2 // Placeholder for last spinbox
|
visible: layoutVector.vecSize === 2 // Placeholder for last spinbox
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap }
|
Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap }
|
||||||
@@ -486,9 +505,12 @@ Section {
|
|||||||
model: root.propertiesModel
|
model: root.propertiesModel
|
||||||
row: index
|
row: index
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyLabel {
|
PropertyLabel {
|
||||||
text: propertyName
|
text: propertyName
|
||||||
tooltip: propertyType
|
tooltip: propertyType
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.topMargin: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -540,7 +562,8 @@ Section {
|
|||||||
for (let i = 0; i < vecSize; ++i) {
|
for (let i = 0; i < vecSize; ++i) {
|
||||||
var newProxyValue = propertyRow.createProxyBackendValue()
|
var newProxyValue = propertyRow.createProxyBackendValue()
|
||||||
loader.item.proxyValues.push(newProxyValue)
|
loader.item.proxyValues.push(newProxyValue)
|
||||||
newProxyValue.valueChangedQml.connect(loader.item.updateExpression)
|
newProxyValue.valueChangedQml.connect(loader.item.updateExpressionFromValue)
|
||||||
|
newProxyValue.expressionChangedQml.connect(loader.item.updateExpressionFromExpression)
|
||||||
loader.item.spinBoxes[i].backendValue = newProxyValue
|
loader.item.spinBoxes[i].backendValue = newProxyValue
|
||||||
}
|
}
|
||||||
propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues)
|
propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues)
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ Item {
|
|||||||
property alias realDragRange: spinBox.realDragRange
|
property alias realDragRange: spinBox.realDragRange
|
||||||
property alias pixelsPerUnit: spinBox.pixelsPerUnit
|
property alias pixelsPerUnit: spinBox.pixelsPerUnit
|
||||||
|
|
||||||
|
property alias actionIndicatorEnabled: spinBox.actionIndicator.enabled
|
||||||
|
property alias actionIndicatorVisible: spinBox.actionIndicatorVisible
|
||||||
|
|
||||||
width: 96
|
width: 96
|
||||||
implicitHeight: spinBox.height
|
implicitHeight: spinBox.height
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,8 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression)
|
|||||||
if (m_expression != expression) {
|
if (m_expression != expression) {
|
||||||
setExpression(expression);
|
setExpression(expression);
|
||||||
m_value.clear();
|
m_value.clear();
|
||||||
emit expressionChanged(nameAsQString()); // Note that we set the name in this case
|
emit expressionChanged(nameAsQString());
|
||||||
|
emit expressionChangedQml();// Note that we set the name in this case
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ bool PropertyEditorValue::isInSubState() const
|
|||||||
bool PropertyEditorValue::isBound() const
|
bool PropertyEditorValue::isBound() const
|
||||||
{
|
{
|
||||||
const QmlObjectNode objectNode(modelNode());
|
const QmlObjectNode objectNode(modelNode());
|
||||||
return objectNode.isValid() && objectNode.hasBindingProperty(name());
|
return m_forceBound || (objectNode.isValid() && objectNode.hasBindingProperty(name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PropertyEditorValue::isInModel() const
|
bool PropertyEditorValue::isInModel() const
|
||||||
@@ -334,6 +335,7 @@ void PropertyEditorValue::resetValue()
|
|||||||
m_expression = QString();
|
m_expression = QString();
|
||||||
emit valueChanged(nameAsQString(), QVariant());
|
emit valueChanged(nameAsQString(), QVariant());
|
||||||
emit expressionChanged({});
|
emit expressionChanged({});
|
||||||
|
emit expressionChangedQml();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +427,34 @@ QStringList PropertyEditorValue::getExpressionAsList() const
|
|||||||
return generateStringList(expression());
|
return generateStringList(expression());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<double> PropertyEditorValue::getExpressionAsVector() const
|
||||||
|
{
|
||||||
|
const QRegularExpression rx(
|
||||||
|
QRegularExpression::anchoredPattern("Qt.vector(2|3|4)d\\((.*?)\\)"));
|
||||||
|
const QRegularExpressionMatch match = rx.match(expression());
|
||||||
|
if (!match.hasMatch())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const QStringList floats = match.captured(2).split(',', Qt::SkipEmptyParts);
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
const int num = match.captured(1).toInt();
|
||||||
|
|
||||||
|
if (num != floats.count())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QVector<double> ret;
|
||||||
|
for (const QString &number : floats) {
|
||||||
|
ret.append(number.toDouble(&ok));
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
bool PropertyEditorValue::idListAdd(const QString &value)
|
bool PropertyEditorValue::idListAdd(const QString &value)
|
||||||
{
|
{
|
||||||
const QmlObjectNode objectNode(modelNode());
|
const QmlObjectNode objectNode(modelNode());
|
||||||
@@ -507,6 +537,15 @@ void PropertyEditorValue::openMaterialEditor(int idx)
|
|||||||
m_modelNode.view()->emitCustomNotification("select_material", {}, {idx});
|
m_modelNode.view()->emitCustomNotification("select_material", {}, {idx});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PropertyEditorValue::setForceBound(bool b)
|
||||||
|
{
|
||||||
|
if (m_forceBound == b)
|
||||||
|
return;
|
||||||
|
m_forceBound = b;
|
||||||
|
|
||||||
|
emit isBoundChanged();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList PropertyEditorValue::generateStringList(const QString &string) const
|
QStringList PropertyEditorValue::generateStringList(const QString &string) const
|
||||||
{
|
{
|
||||||
QString copy = string;
|
QString copy = string;
|
||||||
|
|||||||
@@ -126,12 +126,15 @@ public:
|
|||||||
bool isIdList() const;
|
bool isIdList() const;
|
||||||
|
|
||||||
Q_INVOKABLE QStringList getExpressionAsList() const;
|
Q_INVOKABLE QStringList getExpressionAsList() const;
|
||||||
|
Q_INVOKABLE QVector<double> getExpressionAsVector() const;
|
||||||
Q_INVOKABLE bool idListAdd(const QString &value);
|
Q_INVOKABLE bool idListAdd(const QString &value);
|
||||||
Q_INVOKABLE bool idListRemove(int idx);
|
Q_INVOKABLE bool idListRemove(int idx);
|
||||||
Q_INVOKABLE bool idListReplace(int idx, const QString &value);
|
Q_INVOKABLE bool idListReplace(int idx, const QString &value);
|
||||||
Q_INVOKABLE void commitDrop(const QString &dropData);
|
Q_INVOKABLE void commitDrop(const QString &dropData);
|
||||||
Q_INVOKABLE void openMaterialEditor(int idx);
|
Q_INVOKABLE void openMaterialEditor(int idx);
|
||||||
|
|
||||||
|
Q_INVOKABLE void setForceBound(bool b);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void resetValue();
|
void resetValue();
|
||||||
void setEnumeration(const QString &scope, const QString &name);
|
void setEnumeration(const QString &scope, const QString &name);
|
||||||
@@ -143,6 +146,8 @@ signals:
|
|||||||
void expressionChanged(const QString &name); // HACK - We use the same notifer for the backend and frontend.
|
void expressionChanged(const QString &name); // HACK - We use the same notifer for the backend and frontend.
|
||||||
// If name is empty the signal is used for QML.
|
// If name is empty the signal is used for QML.
|
||||||
|
|
||||||
|
void expressionChangedQml();
|
||||||
|
|
||||||
void exportPropertyAsAliasRequested(const QString &name);
|
void exportPropertyAsAliasRequested(const QString &name);
|
||||||
void removeAliasExportRequested(const QString &name);
|
void removeAliasExportRequested(const QString &name);
|
||||||
|
|
||||||
@@ -168,6 +173,7 @@ private:
|
|||||||
bool m_hasActiveDrag = false;
|
bool m_hasActiveDrag = false;
|
||||||
bool m_isValid = false; // if the property value belongs to a non-existing complexProperty it is invalid
|
bool m_isValid = false; // if the property value belongs to a non-existing complexProperty it is invalid
|
||||||
PropertyEditorNodeWrapper *m_complexNode;
|
PropertyEditorNodeWrapper *m_complexNode;
|
||||||
|
bool m_forceBound = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
|||||||
Reference in New Issue
Block a user