forked from qt-creator/qt-creator
QmlDesigner: Add support for Behaviours
A Behavior will be added as a normal ModelNode to the default property, but we store the property name in behaviorPropertyName. The value of behaviorPropertyName cannot be changed after the ModelNode was created, since I do not see any use case and it keeps things simple. Change-Id: I69ba1d4d706432cfbbd35b001238f623e6e0b4fd Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Marco Bubke <marco.bubke@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -116,8 +116,9 @@ public:
|
|||||||
int minorVersion,
|
int minorVersion,
|
||||||
const PropertyListType &propertyList = PropertyListType(),
|
const PropertyListType &propertyList = PropertyListType(),
|
||||||
const PropertyListType &auxPropertyList = PropertyListType(),
|
const PropertyListType &auxPropertyList = PropertyListType(),
|
||||||
const QString &nodeSource = QString(),
|
const QString &nodeSource = {},
|
||||||
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource);
|
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource,
|
||||||
|
const QString &behaviorPropertyName = {});
|
||||||
|
|
||||||
ModelNode rootModelNode() const;
|
ModelNode rootModelNode() const;
|
||||||
ModelNode rootModelNode();
|
ModelNode rootModelNode();
|
||||||
|
@@ -238,6 +238,7 @@ public:
|
|||||||
bool isComponent() const;
|
bool isComponent() const;
|
||||||
bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
|
bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
|
||||||
QIcon typeIcon() const;
|
QIcon typeIcon() const;
|
||||||
|
QString behaviorPropertyName() const;
|
||||||
|
|
||||||
friend void swap(ModelNode &first, ModelNode &second) noexcept
|
friend void swap(ModelNode &first, ModelNode &second) noexcept
|
||||||
{
|
{
|
||||||
|
@@ -102,9 +102,10 @@ ModelNode AbstractView::createModelNode(const TypeName &typeName,
|
|||||||
const QList<QPair<PropertyName, QVariant>> &propertyList,
|
const QList<QPair<PropertyName, QVariant>> &propertyList,
|
||||||
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
|
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
|
||||||
const QString &nodeSource,
|
const QString &nodeSource,
|
||||||
ModelNode::NodeSourceType nodeSourceType)
|
ModelNode::NodeSourceType nodeSourceType,
|
||||||
|
const QString &behaviorPropertyName)
|
||||||
{
|
{
|
||||||
return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType), model(), this);
|
return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType, behaviorPropertyName), model(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -389,5 +389,15 @@ void InternalNode::setNodeSourceType(int i)
|
|||||||
m_nodeSourceType = i;
|
m_nodeSourceType = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString InternalNode::behaviorPropertyName() const
|
||||||
|
{
|
||||||
|
return m_behaviorPropertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalNode::setBehaviorPropertyName(const QString &name)
|
||||||
|
{
|
||||||
|
m_behaviorPropertyName = name;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -125,6 +125,9 @@ public:
|
|||||||
int nodeSourceType() const;
|
int nodeSourceType() const;
|
||||||
void setNodeSourceType(int i);
|
void setNodeSourceType(int i);
|
||||||
|
|
||||||
|
QString behaviorPropertyName() const;
|
||||||
|
void setBehaviorPropertyName(const QString &name);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Pointer internalPointer() const;
|
Pointer internalPointer() const;
|
||||||
void setInternalWeakPointer(const Pointer &pointer);
|
void setInternalWeakPointer(const Pointer &pointer);
|
||||||
@@ -151,6 +154,8 @@ private:
|
|||||||
|
|
||||||
QString m_nodeSource;
|
QString m_nodeSource;
|
||||||
int m_nodeSourceType = 0;
|
int m_nodeSourceType = 0;
|
||||||
|
|
||||||
|
QString m_behaviorPropertyName;
|
||||||
};
|
};
|
||||||
|
|
||||||
Utils::QHashValueType qHash(const InternalNodePointer& node);
|
Utils::QHashValueType qHash(const InternalNodePointer& node);
|
||||||
|
@@ -97,9 +97,11 @@ ModelPrivate::ModelPrivate(Model *model)
|
|||||||
0,
|
0,
|
||||||
PropertyListType(),
|
PropertyListType(),
|
||||||
PropertyListType(),
|
PropertyListType(),
|
||||||
QString(),
|
{},
|
||||||
ModelNode::NodeWithoutSource,
|
ModelNode::NodeWithoutSource,
|
||||||
|
{},
|
||||||
true);
|
true);
|
||||||
|
|
||||||
m_currentStateNode = m_rootInternalNode;
|
m_currentStateNode = m_rootInternalNode;
|
||||||
m_currentTimelineNode = m_rootInternalNode;
|
m_currentTimelineNode = m_rootInternalNode;
|
||||||
}
|
}
|
||||||
@@ -250,6 +252,7 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
|
|||||||
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
|
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
|
||||||
const QString &nodeSource,
|
const QString &nodeSource,
|
||||||
ModelNode::NodeSourceType nodeSourceType,
|
ModelNode::NodeSourceType nodeSourceType,
|
||||||
|
const QString &behaviorPropertyName,
|
||||||
bool isRootNode)
|
bool isRootNode)
|
||||||
{
|
{
|
||||||
if (typeName.isEmpty())
|
if (typeName.isEmpty())
|
||||||
@@ -263,6 +266,8 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
|
|||||||
InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId);
|
InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId);
|
||||||
newNode->setNodeSourceType(nodeSourceType);
|
newNode->setNodeSourceType(nodeSourceType);
|
||||||
|
|
||||||
|
newNode->setBehaviorPropertyName(behaviorPropertyName);
|
||||||
|
|
||||||
using PropertyPair = QPair<PropertyName, QVariant>;
|
using PropertyPair = QPair<PropertyName, QVariant>;
|
||||||
|
|
||||||
for (const PropertyPair &propertyPair : propertyList) {
|
for (const PropertyPair &propertyPair : propertyList) {
|
||||||
|
@@ -103,6 +103,7 @@ public:
|
|||||||
const QList<QPair<PropertyName, QVariant> > &auxPropertyList,
|
const QList<QPair<PropertyName, QVariant> > &auxPropertyList,
|
||||||
const QString &nodeSource,
|
const QString &nodeSource,
|
||||||
ModelNode::NodeSourceType nodeSourceType,
|
ModelNode::NodeSourceType nodeSourceType,
|
||||||
|
const QString &behaviorPropertyName,
|
||||||
bool isRootNode = false);
|
bool isRootNode = false);
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1436,4 +1436,12 @@ QIcon ModelNode::typeIcon() const
|
|||||||
return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png"));
|
return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ModelNode::behaviorPropertyName() const
|
||||||
|
{
|
||||||
|
if (m_internalNode.isNull())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return m_internalNode->behaviorPropertyName();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -218,6 +218,10 @@ QString QmlTextGenerator::toQml(const ModelNode &node, int indentDepth) const
|
|||||||
result = alias + '.';
|
result = alias + '.';
|
||||||
|
|
||||||
result += type;
|
result += type;
|
||||||
|
if (!node.behaviorPropertyName().isEmpty()) {
|
||||||
|
result += " on " + node.behaviorPropertyName();
|
||||||
|
}
|
||||||
|
|
||||||
result += QStringLiteral(" {\n");
|
result += QStringLiteral(" {\n");
|
||||||
|
|
||||||
const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize;
|
const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize;
|
||||||
|
@@ -1183,6 +1183,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
|
|||||||
ReadingContext *context,
|
ReadingContext *context,
|
||||||
DifferenceHandler &differenceHandler)
|
DifferenceHandler &differenceHandler)
|
||||||
{
|
{
|
||||||
|
auto binding = AST::cast<AST::UiObjectBinding *>(astNode);
|
||||||
|
|
||||||
|
const bool hasOnToken = binding && binding->hasOnToken;
|
||||||
|
|
||||||
|
QString onTokenProperty;
|
||||||
|
|
||||||
|
if (hasOnToken)
|
||||||
|
onTokenProperty = toString(binding->qualifiedId);
|
||||||
|
|
||||||
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
|
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
|
||||||
AST::UiObjectInitializer *astInitializer = initializerOfObject(astNode);
|
AST::UiObjectInitializer *astInitializer = initializerOfObject(astNode);
|
||||||
|
|
||||||
@@ -1219,10 +1228,10 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
|
|||||||
|
|
||||||
bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
|
bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
|
||||||
|
|
||||||
|
if (modelNode.type()
|
||||||
if (modelNode.type() != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then.
|
!= typeName //If there is no valid parentProperty //the node has just been created. The type is correct then.
|
||||||
|| modelNode.majorVersion() != majorVersion
|
|| modelNode.majorVersion() != majorVersion || modelNode.minorVersion() != minorVersion
|
||||||
|| modelNode.minorVersion() != minorVersion) {
|
|| modelNode.behaviorPropertyName() != onTokenProperty) {
|
||||||
const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
|
const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
|
||||||
differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
|
differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
|
||||||
majorVersion, minorVersion,
|
majorVersion, minorVersion,
|
||||||
@@ -1289,7 +1298,8 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
|
|||||||
} else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) {
|
} else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) {
|
||||||
const QString astPropertyName = toString(binding->qualifiedId);
|
const QString astPropertyName = toString(binding->qualifiedId);
|
||||||
if (binding->hasOnToken) {
|
if (binding->hasOnToken) {
|
||||||
// skip value sources
|
// Store Behaviours in the default property
|
||||||
|
defaultPropertyItems.append(member);
|
||||||
} else {
|
} else {
|
||||||
const Value *propertyType = nullptr;
|
const Value *propertyType = nullptr;
|
||||||
const ObjectValue *containingObject = nullptr;
|
const ObjectValue *containingObject = nullptr;
|
||||||
@@ -1685,6 +1695,13 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
|
|||||||
{
|
{
|
||||||
QString nodeSource;
|
QString nodeSource;
|
||||||
|
|
||||||
|
auto binding = AST::cast<AST::UiObjectBinding *>(astNode);
|
||||||
|
|
||||||
|
const bool hasOnToken = binding && binding->hasOnToken;
|
||||||
|
|
||||||
|
QString onTokenProperty;
|
||||||
|
if (hasOnToken)
|
||||||
|
onTokenProperty = toString(binding->qualifiedId);
|
||||||
|
|
||||||
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
|
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
|
||||||
|
|
||||||
@@ -1716,7 +1733,8 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
|
|||||||
PropertyListType(),
|
PropertyListType(),
|
||||||
PropertyListType(),
|
PropertyListType(),
|
||||||
nodeSource,
|
nodeSource,
|
||||||
nodeSourceType);
|
nodeSourceType,
|
||||||
|
onTokenProperty);
|
||||||
|
|
||||||
syncNode(newNode, astNode, context, differenceHandler);
|
syncNode(newNode, astNode, context, differenceHandler);
|
||||||
return newNode;
|
return newNode;
|
||||||
|
@@ -1248,6 +1248,103 @@ void tst_TestCore::testRewriterReparentToNewNode()
|
|||||||
QCOMPARE(testRewriterView->allModelNodes().count(), 8);
|
QCOMPARE(testRewriterView->allModelNodes().count(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_TestCore::testRewriterBehaivours()
|
||||||
|
{
|
||||||
|
const QLatin1String qmlString("\n"
|
||||||
|
"import QtQuick 2.0\n"
|
||||||
|
"\n"
|
||||||
|
"Item {\n"
|
||||||
|
" Item {}\n"
|
||||||
|
" Item {}\n"
|
||||||
|
" Behavior on width {\n"
|
||||||
|
" NumberAnimation { duration: 1000 }\n"
|
||||||
|
" }\n"
|
||||||
|
" Item {}\n"
|
||||||
|
"}\n");
|
||||||
|
|
||||||
|
|
||||||
|
QPlainTextEdit textEdit;
|
||||||
|
textEdit.setPlainText(qmlString);
|
||||||
|
NotIndentingTextEditModifier modifier(&textEdit);
|
||||||
|
|
||||||
|
QScopedPointer<Model> model(Model::create("QtQuick.Rectangle"));
|
||||||
|
|
||||||
|
QScopedPointer<TestRewriterView> testRewriterView(new TestRewriterView(0, RewriterView::Amend));
|
||||||
|
testRewriterView->setTextModifier(&modifier);
|
||||||
|
model->attachView(testRewriterView.data());
|
||||||
|
|
||||||
|
QVERIFY(testRewriterView->errors().isEmpty());
|
||||||
|
|
||||||
|
ModelNode rootModelNode = testRewriterView->rootModelNode();
|
||||||
|
QVERIFY(rootModelNode.isValid());
|
||||||
|
|
||||||
|
const QList<ModelNode> children = rootModelNode.directSubModelNodes();
|
||||||
|
|
||||||
|
QCOMPARE(children.count(), 4);
|
||||||
|
|
||||||
|
ModelNode behavior;
|
||||||
|
for (const ModelNode &child : children) {
|
||||||
|
if (child.type() == "QtQuick.Behavior")
|
||||||
|
behavior = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY(behavior.isValid());
|
||||||
|
QVERIFY(!behavior.behaviorPropertyName().isEmpty());
|
||||||
|
QCOMPARE(behavior.behaviorPropertyName(), "width");
|
||||||
|
|
||||||
|
QVERIFY(!behavior.directSubModelNodes().isEmpty());
|
||||||
|
|
||||||
|
ModelNode animation = behavior.directSubModelNodes().first();
|
||||||
|
|
||||||
|
QVERIFY(animation.isValid());
|
||||||
|
|
||||||
|
NodeMetaInfo metaInfo = behavior.metaInfo();
|
||||||
|
|
||||||
|
QVERIFY(metaInfo.isValid());
|
||||||
|
|
||||||
|
ModelNode newBehavior = testRewriterView->createModelNode("QtQuick.Behavior",
|
||||||
|
metaInfo.majorVersion(),
|
||||||
|
metaInfo.minorVersion(),
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
ModelNode::NodeWithoutSource,
|
||||||
|
"height");
|
||||||
|
|
||||||
|
rootModelNode.defaultNodeListProperty().reparentHere(newBehavior);
|
||||||
|
|
||||||
|
QCOMPARE(newBehavior.behaviorPropertyName(), "height");
|
||||||
|
|
||||||
|
metaInfo = animation.metaInfo();
|
||||||
|
QVERIFY(metaInfo.isValid());
|
||||||
|
ModelNode newAnimation = testRewriterView->createModelNode(metaInfo.typeName(),
|
||||||
|
metaInfo.majorVersion(),
|
||||||
|
metaInfo.minorVersion());
|
||||||
|
|
||||||
|
newBehavior.defaultNodeListProperty().reparentHere(newAnimation);
|
||||||
|
|
||||||
|
newAnimation.variantProperty("duration").setValue(500);
|
||||||
|
|
||||||
|
const QLatin1String expextedQmlCode(
|
||||||
|
"\nimport QtQuick 2.0\n\n"
|
||||||
|
"Item {\n Item {}\n Item {}\n"
|
||||||
|
" Behavior on width {\n "
|
||||||
|
" NumberAnimation { duration: 1000 }\n"
|
||||||
|
" }\n"
|
||||||
|
" Item {}\n\n"
|
||||||
|
" Behavior on height {\n"
|
||||||
|
" NumberAnimation {\n"
|
||||||
|
" duration: 500\n"
|
||||||
|
" }\n }\n}\n");
|
||||||
|
|
||||||
|
|
||||||
|
QCOMPARE(textEdit.toPlainText(), expextedQmlCode);
|
||||||
|
|
||||||
|
newBehavior.destroy();
|
||||||
|
|
||||||
|
QVERIFY(!newBehavior.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
void tst_TestCore::testRewriterForGradientMagic()
|
void tst_TestCore::testRewriterForGradientMagic()
|
||||||
{
|
{
|
||||||
const QLatin1String qmlString("\n"
|
const QLatin1String qmlString("\n"
|
||||||
|
@@ -145,6 +145,7 @@ private slots:
|
|||||||
void testRewriterUnicodeChars();
|
void testRewriterUnicodeChars();
|
||||||
void testRewriterTransactionAddingAfterReparenting();
|
void testRewriterTransactionAddingAfterReparenting();
|
||||||
void testRewriterReparentToNewNode();
|
void testRewriterReparentToNewNode();
|
||||||
|
void testRewriterBehaivours();
|
||||||
|
|
||||||
//
|
//
|
||||||
// unit tests QmlModelNodeFacade/QmlModelState
|
// unit tests QmlModelNodeFacade/QmlModelState
|
||||||
|
Reference in New Issue
Block a user