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 var proxyValues: []
|
||||
property var spinBoxes: [boxX, boxY, boxZ, boxW]
|
||||
property bool block: false
|
||||
|
||||
signal remove
|
||||
|
||||
onVecSizeChanged: updateProxyValues()
|
||||
onVecSizeChanged: layoutVector.updateProxyValues()
|
||||
|
||||
spacing: StudioTheme.Values.sectionRowSpacing / 2
|
||||
spacing: StudioTheme.Values.sectionRowSpacing
|
||||
|
||||
function isValidValue(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) {
|
||||
if (!isValidValue(proxyValues[i].value))
|
||||
if (!layoutVector.isValidValue(layoutVector.proxyValues[i].value))
|
||||
return
|
||||
}
|
||||
|
||||
let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value
|
||||
for (let j=1; j < vecSize; ++j)
|
||||
expStr += ", " + proxyValues[j].value
|
||||
let expStr = "Qt.vector" + layoutVector.vecSize + "d(" + layoutVector.proxyValues[0].value
|
||||
for (let j=1; j < layoutVector.vecSize; ++j)
|
||||
expStr += ", " + layoutVector.proxyValues[j].value
|
||||
expStr += ")"
|
||||
|
||||
layoutVector.backendValue.expression = expStr
|
||||
}
|
||||
|
||||
function updateProxyValues() {
|
||||
if (!backendValue)
|
||||
if (!layoutVector.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 = layoutVector.backendValue.getExpressionAsVector()
|
||||
|
||||
let vals = []
|
||||
for (let i = 0; i < vecSize; ++i) {
|
||||
vals[i] = parseFloat(numbers[i])
|
||||
if (!isValidValue(vals[i]))
|
||||
return
|
||||
layoutVector.block = true
|
||||
|
||||
if (layoutVector.vecSize === vals.length) {
|
||||
for (let j = 0; j < layoutVector.vecSize; ++j) {
|
||||
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)
|
||||
proxyValues[j].value = vals[j]
|
||||
layoutVector.block = false
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
@@ -357,15 +368,18 @@ Section {
|
||||
tooltip: "X"
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
||||
Spacer {
|
||||
implicitWidth: StudioTheme.Values.controlGap
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: boxY
|
||||
actionIndicatorVisible: false
|
||||
minimumValue: -9999999
|
||||
maximumValue: 9999999
|
||||
decimals: 2
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||
@@ -386,14 +400,16 @@ Section {
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
visible: vecSize > 2
|
||||
visible: layoutVector.vecSize > 2
|
||||
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||
|
||||
SpinBox {
|
||||
id: boxZ
|
||||
actionIndicatorVisible: false
|
||||
minimumValue: -9999999
|
||||
maximumValue: 9999999
|
||||
decimals: 2
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||
@@ -401,19 +417,22 @@ Section {
|
||||
ControlLabel {
|
||||
text: "Z"
|
||||
tooltip: "Z"
|
||||
visible: vecSize > 2
|
||||
visible: layoutVector.vecSize > 2
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
||||
Spacer {
|
||||
implicitWidth: StudioTheme.Values.controlGap
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: boxW
|
||||
actionIndicatorVisible: false
|
||||
minimumValue: -9999999
|
||||
maximumValue: 9999999
|
||||
decimals: 2
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
visible: vecSize > 3
|
||||
visible: layoutVector.vecSize > 3
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||
@@ -421,7 +440,7 @@ Section {
|
||||
ControlLabel {
|
||||
text: "W"
|
||||
tooltip: "W"
|
||||
visible: vecSize > 3
|
||||
visible: layoutVector.vecSize > 3
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlGap }
|
||||
@@ -430,7 +449,7 @@ Section {
|
||||
height: 10
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
visible: vecSize === 2 // Placeholder for last spinbox
|
||||
visible: layoutVector.vecSize === 2 // Placeholder for last spinbox
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap }
|
||||
@@ -486,9 +505,12 @@ Section {
|
||||
model: root.propertiesModel
|
||||
row: index
|
||||
}
|
||||
|
||||
PropertyLabel {
|
||||
text: propertyName
|
||||
tooltip: propertyType
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: 6
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -540,7 +562,8 @@ Section {
|
||||
for (let i = 0; i < vecSize; ++i) {
|
||||
var newProxyValue = propertyRow.createProxyBackendValue()
|
||||
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
|
||||
}
|
||||
propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues)
|
||||
|
||||
@@ -22,6 +22,9 @@ Item {
|
||||
property alias realDragRange: spinBox.realDragRange
|
||||
property alias pixelsPerUnit: spinBox.pixelsPerUnit
|
||||
|
||||
property alias actionIndicatorEnabled: spinBox.actionIndicator.enabled
|
||||
property alias actionIndicatorVisible: spinBox.actionIndicatorVisible
|
||||
|
||||
width: 96
|
||||
implicitHeight: spinBox.height
|
||||
|
||||
|
||||
@@ -153,7 +153,8 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression)
|
||||
if (m_expression != expression) {
|
||||
setExpression(expression);
|
||||
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
|
||||
{
|
||||
const QmlObjectNode objectNode(modelNode());
|
||||
return objectNode.isValid() && objectNode.hasBindingProperty(name());
|
||||
return m_forceBound || (objectNode.isValid() && objectNode.hasBindingProperty(name()));
|
||||
}
|
||||
|
||||
bool PropertyEditorValue::isInModel() const
|
||||
@@ -334,6 +335,7 @@ void PropertyEditorValue::resetValue()
|
||||
m_expression = QString();
|
||||
emit valueChanged(nameAsQString(), QVariant());
|
||||
emit expressionChanged({});
|
||||
emit expressionChangedQml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +427,34 @@ QStringList PropertyEditorValue::getExpressionAsList() const
|
||||
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)
|
||||
{
|
||||
const QmlObjectNode objectNode(modelNode());
|
||||
@@ -507,6 +537,15 @@ void PropertyEditorValue::openMaterialEditor(int 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
|
||||
{
|
||||
QString copy = string;
|
||||
|
||||
@@ -126,12 +126,15 @@ public:
|
||||
bool isIdList() const;
|
||||
|
||||
Q_INVOKABLE QStringList getExpressionAsList() const;
|
||||
Q_INVOKABLE QVector<double> getExpressionAsVector() const;
|
||||
Q_INVOKABLE bool idListAdd(const QString &value);
|
||||
Q_INVOKABLE bool idListRemove(int idx);
|
||||
Q_INVOKABLE bool idListReplace(int idx, const QString &value);
|
||||
Q_INVOKABLE void commitDrop(const QString &dropData);
|
||||
Q_INVOKABLE void openMaterialEditor(int idx);
|
||||
|
||||
Q_INVOKABLE void setForceBound(bool b);
|
||||
|
||||
public slots:
|
||||
void resetValue();
|
||||
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.
|
||||
// If name is empty the signal is used for QML.
|
||||
|
||||
void expressionChangedQml();
|
||||
|
||||
void exportPropertyAsAliasRequested(const QString &name);
|
||||
void removeAliasExportRequested(const QString &name);
|
||||
|
||||
@@ -168,6 +173,7 @@ private:
|
||||
bool m_hasActiveDrag = false;
|
||||
bool m_isValid = false; // if the property value belongs to a non-existing complexProperty it is invalid
|
||||
PropertyEditorNodeWrapper *m_complexNode;
|
||||
bool m_forceBound = false;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
||||
Reference in New Issue
Block a user