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:
Thomas Hartmann
2022-07-13 12:07:58 +02:00
parent c505d6a72e
commit aeea22257c
12 changed files with 172 additions and 20 deletions

View File

@@ -112,12 +112,13 @@ public:
ModelNode createModelNode(const TypeName &typeName);
ModelNode createModelNode(const TypeName &typeName,
int majorVersion,
int minorVersion,
const PropertyListType &propertyList = PropertyListType(),
const PropertyListType &auxPropertyList = PropertyListType(),
const QString &nodeSource = QString(),
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource);
int majorVersion,
int minorVersion,
const PropertyListType &propertyList = PropertyListType(),
const PropertyListType &auxPropertyList = PropertyListType(),
const QString &nodeSource = {},
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource,
const QString &behaviorPropertyName = {});
ModelNode rootModelNode() const;
ModelNode rootModelNode();

View File

@@ -238,6 +238,7 @@ public:
bool isComponent() const;
bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
QIcon typeIcon() const;
QString behaviorPropertyName() const;
friend void swap(ModelNode &first, ModelNode &second) noexcept
{

View File

@@ -97,14 +97,15 @@ ModelNode AbstractView::createModelNode(const TypeName &typeName)
}
ModelNode AbstractView::createModelNode(const TypeName &typeName,
int majorVersion,
int minorVersion,
const QList<QPair<PropertyName, QVariant> > &propertyList,
const QList<QPair<PropertyName, QVariant> > &auxPropertyList,
const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType)
int majorVersion,
int minorVersion,
const QList<QPair<PropertyName, QVariant>> &propertyList,
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
const QString &nodeSource,
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);
}

View File

@@ -389,5 +389,15 @@ void InternalNode::setNodeSourceType(int i)
m_nodeSourceType = i;
}
QString InternalNode::behaviorPropertyName() const
{
return m_behaviorPropertyName;
}
void InternalNode::setBehaviorPropertyName(const QString &name)
{
m_behaviorPropertyName = name;
}
}
}

View File

@@ -125,6 +125,9 @@ public:
int nodeSourceType() const;
void setNodeSourceType(int i);
QString behaviorPropertyName() const;
void setBehaviorPropertyName(const QString &name);
protected:
Pointer internalPointer() const;
void setInternalWeakPointer(const Pointer &pointer);
@@ -151,6 +154,8 @@ private:
QString m_nodeSource;
int m_nodeSourceType = 0;
QString m_behaviorPropertyName;
};
Utils::QHashValueType qHash(const InternalNodePointer& node);

View File

@@ -97,9 +97,11 @@ ModelPrivate::ModelPrivate(Model *model)
0,
PropertyListType(),
PropertyListType(),
QString(),
{},
ModelNode::NodeWithoutSource,
{},
true);
m_currentStateNode = m_rootInternalNode;
m_currentTimelineNode = m_rootInternalNode;
}
@@ -250,6 +252,7 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType,
const QString &behaviorPropertyName,
bool isRootNode)
{
if (typeName.isEmpty())
@@ -263,6 +266,8 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId);
newNode->setNodeSourceType(nodeSourceType);
newNode->setBehaviorPropertyName(behaviorPropertyName);
using PropertyPair = QPair<PropertyName, QVariant>;
for (const PropertyPair &propertyPair : propertyList) {

View File

@@ -103,6 +103,7 @@ public:
const QList<QPair<PropertyName, QVariant> > &auxPropertyList,
const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType,
const QString &behaviorPropertyName,
bool isRootNode = false);

View File

@@ -1436,4 +1436,12 @@ QIcon ModelNode::typeIcon() const
return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png"));
}
QString ModelNode::behaviorPropertyName() const
{
if (m_internalNode.isNull())
return {};
return m_internalNode->behaviorPropertyName();
}
}

View File

@@ -218,6 +218,10 @@ QString QmlTextGenerator::toQml(const ModelNode &node, int indentDepth) const
result = alias + '.';
result += type;
if (!node.behaviorPropertyName().isEmpty()) {
result += " on " + node.behaviorPropertyName();
}
result += QStringLiteral(" {\n");
const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize;

View File

@@ -1183,6 +1183,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
ReadingContext *context,
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::UiObjectInitializer *astInitializer = initializerOfObject(astNode);
@@ -1219,10 +1228,10 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
if (modelNode.type() != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then.
|| modelNode.majorVersion() != majorVersion
|| modelNode.minorVersion() != minorVersion) {
if (modelNode.type()
!= typeName //If there is no valid parentProperty //the node has just been created. The type is correct then.
|| modelNode.majorVersion() != majorVersion || modelNode.minorVersion() != minorVersion
|| modelNode.behaviorPropertyName() != onTokenProperty) {
const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
majorVersion, minorVersion,
@@ -1289,7 +1298,8 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
} else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) {
const QString astPropertyName = toString(binding->qualifiedId);
if (binding->hasOnToken) {
// skip value sources
// Store Behaviours in the default property
defaultPropertyItems.append(member);
} else {
const Value *propertyType = nullptr;
const ObjectValue *containingObject = nullptr;
@@ -1685,6 +1695,13 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
{
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);
@@ -1716,7 +1733,8 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
PropertyListType(),
PropertyListType(),
nodeSource,
nodeSourceType);
nodeSourceType,
onTokenProperty);
syncNode(newNode, astNode, context, differenceHandler);
return newNode;

View File

@@ -1248,6 +1248,103 @@ void tst_TestCore::testRewriterReparentToNewNode()
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()
{
const QLatin1String qmlString("\n"

View File

@@ -145,6 +145,7 @@ private slots:
void testRewriterUnicodeChars();
void testRewriterTransactionAddingAfterReparenting();
void testRewriterReparentToNewNode();
void testRewriterBehaivours();
//
// unit tests QmlModelNodeFacade/QmlModelState