From cdf843160f50e886bea8e55b94c9392299cef8b9 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Wed, 2 Apr 2025 12:37:40 +0300 Subject: [PATCH] QmlDesigner: Make it possible to reparent and slide in a transaction NodeListProperty can slide and re-parent nodes in the same transaction Fixes: QDS-7476 Change-Id: Ibc62e2bce5baca9465c320f0dfa1b9d408d04384 Reviewed-by: Thomas Hartmann --- .../filemanager/addobjectvisitor.cpp | 16 +++-- .../filemanager/addobjectvisitor.h | 2 + .../filemanager/qmlrefactoring.cpp | 4 +- .../designercore/filemanager/qmlrefactoring.h | 2 +- .../designercore/filemanager/qmlrewriter.cpp | 41 +++++++++++ .../designercore/filemanager/qmlrewriter.h | 2 + .../designercore/rewriter/rewriteaction.cpp | 71 +++++++++++-------- .../designercore/rewriter/rewriteaction.h | 10 +++ .../rewriter/rewriteactioncompressor.cpp | 43 +++++++++++ .../rewriter/rewriteactioncompressor.h | 1 + 10 files changed, 153 insertions(+), 39 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.cpp b/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.cpp index 9de34ff4c16..2edb8d8fcab 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.cpp +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.cpp @@ -10,12 +10,14 @@ using namespace QmlDesigner::Internal; AddObjectVisitor::AddObjectVisitor(QmlDesigner::TextModifier &modifier, quint32 parentLocation, + quint32 nodeLocation, const QString &content, - const PropertyNameList &propertyOrder): - QMLRewriter(modifier), - m_parentLocation(parentLocation), - m_content(content), - m_propertyOrder(propertyOrder) + const PropertyNameList &propertyOrder) + : QMLRewriter(modifier) + , m_parentLocation(parentLocation) + , m_nodeLocation(nodeLocation) + , m_content(content) + , m_propertyOrder(propertyOrder) { } @@ -44,7 +46,9 @@ bool AddObjectVisitor::visit(QmlJS::AST::UiObjectDefinition *ast) // FIXME: duplicate code in the QmlJS::Rewriter class, remove this void AddObjectVisitor::insertInto(QmlJS::AST::UiObjectInitializer *ast) { - QmlJS::AST::UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); + QmlJS::AST::UiObjectMemberList *insertAfter = searchChildrenToInsertAfter(ast->members, + m_propertyOrder, + m_nodeLocation - 1); int insertionPoint; int depth; diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.h b/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.h index c2aed736f35..b74a7c7d5bb 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.h +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/addobjectvisitor.h @@ -13,6 +13,7 @@ class AddObjectVisitor: public QMLRewriter public: AddObjectVisitor(QmlDesigner::TextModifier &modifier, quint32 parentLocation, + quint32 nodeLocation, const QString &content, const PropertyNameList &propertyOrder); @@ -25,6 +26,7 @@ private: private: quint32 m_parentLocation; + quint32 m_nodeLocation; QString m_content; PropertyNameList m_propertyOrder; }; diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.cpp b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.cpp index 648741293c7..d71cf854e67 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.cpp +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.cpp @@ -75,12 +75,12 @@ bool QmlRefactoring::addToArrayMemberList(int parentLocation, return visit(qmlDocument->qmlProgram()); } -bool QmlRefactoring::addToObjectMemberList(int parentLocation, const QString &content) +bool QmlRefactoring::addToObjectMemberList(int parentLocation, int nodeLocation, const QString &content) { if (parentLocation < 0) return false; - AddObjectVisitor visit(*textModifier, parentLocation, content, m_propertyOrder); + AddObjectVisitor visit(*textModifier, parentLocation, nodeLocation, content, m_propertyOrder); return visit(qmlDocument->qmlProgram()); } diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.h b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.h index ef21f027929..34894eb76f8 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.h +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrefactoring.h @@ -33,7 +33,7 @@ public: bool removeImport(const Import &import); bool addToArrayMemberList(int parentLocation, PropertyNameView propertyName, const QString &content); - bool addToObjectMemberList(int parentLocation, const QString &content); + bool addToObjectMemberList(int parentLocation, int nodeLocation, const QString &content); bool addProperty(int parentLocation, PropertyNameView name, const QString &value, diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.cpp b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.cpp index b5b1feb8b6f..77021f02324 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.cpp +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.cpp @@ -272,6 +272,47 @@ QmlJS::AST::UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter( return nullptr; } +QmlJS::AST::UiObjectMemberList *QMLRewriter::searchChildrenToInsertAfter( + QmlJS::AST::UiObjectMemberList *members, const PropertyNameList &propertyOrder, int pos) +{ + if (pos < 0) + return searchMemberToInsertAfter(members, propertyOrder); + + // An empty property name should be available in the propertyOrder List, which is the right place + // to define the objects there. + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(PropertyName()); + + QmlJS::AST::UiObjectMemberList *lastObjectDef = nullptr; + QmlJS::AST::UiObjectMemberList *lastNonObjectDef = nullptr; + int objectPos = 0; + + for (QmlJS::AST::UiObjectMemberList *iter = members; iter; iter = iter->next) { + QmlJS::AST::UiObjectMember *member = iter->member; + int idx = -1; + + if (QmlJS::AST::cast(member)) { + lastObjectDef = iter; + if (objectPos++ == pos) + break; + } else if (auto arrayBinding = QmlJS::AST::cast(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId).toUtf8()); + else if (auto objectBinding = QmlJS::AST::cast(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId).toUtf8()); + else if (auto scriptBinding = QmlJS::AST::cast(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId).toUtf8()); + else if (QmlJS::AST::cast(member)) + idx = propertyOrder.indexOf("property"); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + void QMLRewriter::dump(const ASTPath &path) { qCDebug(qmlRewriter) << "AST path with" << path.size() << "node(s):"; diff --git a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.h b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.h index efbc25a0e27..e8486d2eddb 100644 --- a/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.h +++ b/src/plugins/qmldesigner/libs/designercore/filemanager/qmlrewriter.h @@ -56,6 +56,8 @@ protected: QmlJS::AST::UiObjectMemberList *members, PropertyNameView propertyName, const PropertyNameList &propertyOrder); + static QmlJS::AST::UiObjectMemberList *searchChildrenToInsertAfter( + QmlJS::AST::UiObjectMemberList *members, const PropertyNameList &propertyOrder, int pos = -1); protected: bool didRewriting() const diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.cpp b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.cpp index 94d5e905aa2..1035f8273a0 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.cpp +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.cpp @@ -63,34 +63,38 @@ QStringView toString(QmlRefactoring::PropertyType type) bool AddPropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore) { if (m_sheduledInHierarchy) { - const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); + const int parentLocation = positionStore.nodeOffset(m_property.parentModelNode()); bool result = false; if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) { - result = refactoring.addToObjectMemberList(nodeLocation, m_valueText); + const int nodeLocation = movedAfterCreation() + ? m_property.toNodeListProperty().indexOf(m_containedModelNode) + : -1; + result = refactoring.addToObjectMemberList(parentLocation, nodeLocation, m_valueText); if (!result) { qDebug() << "*** AddPropertyRewriteAction::execute failed in addToObjectMemberList(" - << nodeLocation << ',' - << m_valueText << ") **" + << parentLocation << ',' << nodeLocation << ',' << m_valueText << ") **" << info(); } } else if (m_property.isNodeListProperty() && m_property.toNodeListProperty().count() > 1) { - result = refactoring.addToArrayMemberList(nodeLocation, m_property.name(), m_valueText); + result = refactoring.addToArrayMemberList(parentLocation, m_property.name(), m_valueText); if (!result) { qDebug() << "*** AddPropertyRewriteAction::execute failed in addToArrayMemberList(" - << nodeLocation << ',' - << m_property.name() << ',' - << m_valueText << ") **" - << info(); + << parentLocation << ',' << m_property.name() << ',' << m_valueText + << ") **" << info(); } } else { - result = refactoring.addProperty(nodeLocation, m_property.name(), m_valueText, m_propertyType, m_property.dynamicTypeName()); + result = refactoring.addProperty(parentLocation, + m_property.name(), + m_valueText, + m_propertyType, + m_property.dynamicTypeName()); if (!result) { qDebug() << "*** AddPropertyRewriteAction::execute failed in addProperty(" - << nodeLocation << ',' << m_property.name() << ',' << m_valueText << "," + << parentLocation << ',' << m_property.name() << ',' << m_valueText << "," << toString(m_propertyType) << ") **" << info(); } } @@ -163,38 +167,44 @@ QString ChangeIdRewriteAction::info() const bool ChangePropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore) { if (m_sheduledInHierarchy) { - const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); - if (nodeLocation < 0) { + const int parentLocation = positionStore.nodeOffset(m_property.parentModelNode()); + if (parentLocation < 0) { qWarning() << "*** ChangePropertyRewriteAction::execute ignored. Invalid node location"; return true; } bool result = false; if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) { - result = refactoring.addToObjectMemberList(nodeLocation, m_valueText); + const int nodeLocation = movedAfterCreation() + ? m_property.toNodeListProperty().indexOf(m_containedModelNode) + : -1; + + result = refactoring.addToObjectMemberList(parentLocation, nodeLocation, m_valueText); if (!result) { - qDebug() << "*** ChangePropertyRewriteAction::execute failed in addToObjectMemberList(" - << nodeLocation << ',' - << m_valueText << ") **" - << info(); + qDebug() + << "*** ChangePropertyRewriteAction::execute failed in addToObjectMemberList(" + << parentLocation << ',' << nodeLocation << ',' << m_valueText << ") **" + << info(); } } else if (m_propertyType == QmlRefactoring::ArrayBinding) { - result = refactoring.addToArrayMemberList(nodeLocation, m_property.name(), m_valueText); + result = refactoring.addToArrayMemberList(parentLocation, m_property.name(), m_valueText); if (!result) { - qDebug() << "*** ChangePropertyRewriteAction::execute failed in addToArrayMemberList(" - << nodeLocation << ',' - << m_property.name() << ',' - << m_valueText << ") **" - << info(); + qDebug() + << "*** ChangePropertyRewriteAction::execute failed in addToArrayMemberList(" + << parentLocation << ',' << m_property.name() << ',' << m_valueText << ") **" + << info(); } } else { - result = refactoring.changeProperty(nodeLocation, m_property.name(), m_valueText, m_propertyType); + result = refactoring.changeProperty(parentLocation, + m_property.name(), + m_valueText, + m_propertyType); if (!result) { qDebug() << "*** ChangePropertyRewriteAction::execute failed in changeProperty(" - << nodeLocation << ',' << m_property.name() << ',' << m_valueText << ',' + << parentLocation << ',' << m_property.name() << ',' << m_valueText << ',' << toString(m_propertyType) << ") **" << info(); } } @@ -327,12 +337,13 @@ bool MoveNodeRewriteAction::execute(QmlRefactoring &refactoring, bool inDefaultProperty = (m_movingNode.parentProperty().parentModelNode().metaInfo().defaultPropertyName() == m_movingNode.parentProperty().name()); - result = refactoring.moveObjectBeforeObject(movingNodeLocation, newTrailingNodeLocation, inDefaultProperty); + result = refactoring.moveObjectBeforeObject(movingNodeLocation, + newTrailingNodeLocation, + inDefaultProperty); if (!result) { qDebug() << "*** MoveNodeRewriteAction::execute failed in moveObjectBeforeObject(" - << movingNodeLocation << ',' - << newTrailingNodeLocation << ") **" - << info(); + << movingNodeLocation << ',' << newTrailingNodeLocation << ',' << inDefaultProperty + << ") **" << info(); } return result; diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.h b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.h index debfb613123..a1f77f70d64 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.h +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteaction.h @@ -73,12 +73,16 @@ public: ModelNode containedModelNode() const { return m_containedModelNode; } + bool movedAfterCreation() const { return m_movedAfterCreation; } + void setMovedAfterCreation(bool moved) { m_movedAfterCreation = moved; } + private: AbstractProperty m_property; QString m_valueText; QmlDesigner::QmlRefactoring::PropertyType m_propertyType; ModelNode m_containedModelNode; bool m_sheduledInHierarchy; + bool m_movedAfterCreation = false; }; class ChangeIdRewriteAction: public RewriteAction @@ -127,12 +131,16 @@ public: ModelNode containedModelNode() const { return m_containedModelNode; } + bool movedAfterCreation() const { return m_movedAfterCreation; } + void setMovedAfterCreation(bool moved) { m_movedAfterCreation = moved; } + private: AbstractProperty m_property; QString m_valueText; QmlDesigner::QmlRefactoring::PropertyType m_propertyType; ModelNode m_containedModelNode; bool m_sheduledInHierarchy; + bool m_movedAfterCreation; }; class ChangeTypeRewriteAction:public RewriteAction @@ -238,6 +246,8 @@ public: MoveNodeRewriteAction *asMoveNodeRewriteAction() override { return this; } + ModelNode movingNode() const { return m_movingNode; } + private: ModelNode m_movingNode; ModelNode m_newTrailingNode; diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.cpp b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.cpp index edee6840ef6..f167604e6d8 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.cpp +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.cpp @@ -39,6 +39,7 @@ void RewriteActionCompressor::operator()(QList &actions, compressAddEditRemoveNodeActions(actions); compressAddEditActions(actions, tabSettings); compressAddReparentActions(actions); + compressSlidesIntoNewNode(actions); } void RewriteActionCompressor::compressImports(QList &actions) const @@ -378,3 +379,45 @@ void RewriteActionCompressor::compressAddReparentActions(QList delete action; } } + +void RewriteActionCompressor::compressSlidesIntoNewNode(QList &actions) const +{ + QList actionsToRemove; + QMap addedNodes; + for (RewriteAction *action : std::as_const(actions)) { + if (action->asAddPropertyRewriteAction() || action->asChangePropertyRewriteAction()) { + ModelNode containedNode; + + if (AddPropertyRewriteAction *addAction = action->asAddPropertyRewriteAction()) + containedNode = addAction->containedModelNode(); + else if (ChangePropertyRewriteAction *changeAction = action->asChangePropertyRewriteAction()) + containedNode = changeAction->containedModelNode(); + + if (!containedNode.isValid()) + continue; + + if (m_positionStore->nodeOffset(containedNode) != ModelNodePositionStorage::INVALID_LOCATION) + continue; + + addedNodes.insert(containedNode, action); + } else if (MoveNodeRewriteAction *moveNodeAction = action->asMoveNodeRewriteAction()) { + RewriteAction *previousAction = addedNodes[moveNodeAction->movingNode()]; + if (!previousAction) + continue; + + if (AddPropertyRewriteAction *addAction = previousAction->asAddPropertyRewriteAction()) { + actionsToRemove.append(moveNodeAction); + addAction->setMovedAfterCreation(true); + } else if (ChangePropertyRewriteAction *changeAction = previousAction + ->asChangePropertyRewriteAction()) { + actionsToRemove.append(moveNodeAction); + changeAction->setMovedAfterCreation(true); + } + } + } + + for (RewriteAction *action : std::as_const(actionsToRemove)) { + actions.removeOne(action); + delete action; + } +} diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.h b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.h index 862a31b1892..e7e13becbbf 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.h +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriteactioncompressor.h @@ -28,6 +28,7 @@ private: void compressPropertyActions(QList &actions) const; void compressAddEditActions(QList &actions, const TextEditor::TabSettings &tabSettings) const; void compressAddReparentActions(QList &actions) const; + void compressSlidesIntoNewNode(QList &actions) const; private: PropertyNameList m_propertyOrder;