From d829f57706a6b773d90b226d18a35621867f1d72 Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Thu, 27 Feb 2025 15:59:41 +0200 Subject: [PATCH] QmlDesigner: Implement extract component feature A feature in the navigator where user can click a context menu button to extract a component into the the parent view Task-number: QDS-14799 Change-Id: I415fc521a226574489c38c95b0167b433412c8be Reviewed-by: Miikka Heikkinen --- .../componentcore/componentcore_constants.h | 3 + .../componentcore/designeractionmanager.cpp | 15 +++- .../modelnodecontextmenu_helper.h | 14 +++ .../componentcore/modelnodeoperations.cpp | 88 +++++++++++++++++++ .../componentcore/modelnodeoperations.h | 1 + 5 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index c59bef9ace3..0e6e572b635 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -72,6 +72,7 @@ inline constexpr char jumpToCodeCommandId[] = "JumpToCode"; inline constexpr char mergeTemplateCommandId[] = "MergeTemplate"; inline constexpr char goToImplementationCommandId[] = "GoToImplementation"; inline constexpr char makeComponentCommandId[] = "MakeComponent"; +inline constexpr char extractComponentCommandId[] = "ExtractComponent"; inline constexpr char importComponentCommandId[] = "ImportComponent"; inline constexpr char exportComponentCommandId[] = "ExportComponent"; inline constexpr char editMaterialCommandId[] = "EditMaterial"; @@ -167,6 +168,8 @@ inline constexpr char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDe "Go to Implementation"); inline constexpr char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Component"); +inline constexpr char extractComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Extract Component"); inline constexpr char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material"); inline constexpr char addToContentLibraryDisplayName[] = QT_TRANSLATE_NOOP( diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 2c79c0f9580..7ab5010607e 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -1958,7 +1958,7 @@ void DesignerActionManager::createDefaultDesignerActions() contextIcon(DesignerIcons::EnterComponentIcon), rootCategory, QKeySequence(Qt::Key_F2), - Priorities::ComponentActions + 4, + Priorities::ComponentActions + 5, &goIntoComponentOperation, &selectionIsEditableComponent)); @@ -2027,13 +2027,24 @@ void DesignerActionManager::createDefaultDesignerActions() &singleSelection, &singleSelection)); + addDesignerAction(new ModelNodeContextMenuAction( + extractComponentCommandId, + extractComponentDisplayName, + contextIcon(DesignerIcons::MakeComponentIcon), + rootCategory, + QKeySequence(), + Priorities::ComponentActions + 3, + &extractComponent, + &singleSelection, + &isFileComponent)); + addDesignerAction(new ModelNodeContextMenuAction( editInEffectComposerCommandId, editInEffectComposerDisplayName, contextIcon(DesignerIcons::EditIcon), rootCategory, QKeySequence(), - Priorities::ComponentActions + 3, + Priorities::ComponentActions + 4, &editInEffectComposer, &SelectionContextFunctors::always, // If action is visible, it is usable &singleSelectionEffectComposer)); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index f645975d0fd..222943024fa 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -48,6 +48,20 @@ inline bool inBaseState(const SelectionContext &selectionState) return selectionState.isInBaseState(); } +inline bool isFileComponent(const SelectionContext &selectionContext) +{ + //TODO: FLAG to hide/show the action until it's completed + bool shouldShowAction = false; + if (shouldShowAction && selectionContext.isValid() && selectionContext.singleNodeIsSelected()) { + ModelNode node = selectionContext.currentSingleSelectedNode(); + if (node.hasMetaInfo()) { + NodeMetaInfo nodeInfo = node.metaInfo(); + return nodeInfo.isFileComponent(); + } + } + return false; +} + inline bool singleSelection(const SelectionContext &selectionState) { return selectionState.singleNodeIsSelected(); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 9210554c4bd..48b3764fd81 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -14,12 +14,15 @@ #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -59,6 +62,7 @@ #include #include +#include #include #include #include @@ -865,6 +869,90 @@ void moveToComponent(const SelectionContext &selectionContext) selectionContext.view()->model()->rewriterView()->moveToComponent(modelNode); } +void extractComponent(const SelectionContext &selectionContext) +{ + ModelNode selectedNode = selectionContext.currentSingleSelectedNode(); + AbstractView *contextView = selectionContext.view(); + + // Get the path of the qml component + QString filePath = ModelUtils::componentFilePath(selectedNode); + if (filePath.isEmpty()) { + qWarning() << "Qml file for component " << selectedNode.displayName() << "not found!"; + return; + } + + // Read the content of the qml component + QString componentText; + Utils::FilePath path = Utils::FilePath::fromString(filePath); + Utils::FileReader reader; + if (!reader.fetch(path)) { + qWarning() << "Cannot open component file " << filePath; + return; + } + componentText = QString::fromUtf8(reader.data()); + +#ifdef QDS_USE_PROJECTSTORAGE + ModelPointer inputModel = contextView->model()->createModel("Rectangle"); +#else + ModelPointer inputModel = Model::create("QtQuick.Rectangle", 1, 0, contextView->model()); + inputModel->setFileUrl(contextView->model()->fileUrl()); +#endif + + // Create ModelNodes from qml string + // This is not including the root node by default + QPlainTextEdit textEdit; + QString imports; + const QList modelImports = contextView->model()->imports(); + for (const Import &import : modelImports) + imports += "import " + import.toString(true) + QLatin1Char('\n'); + + textEdit.setPlainText(imports + componentText); + NotIndentingTextEditModifier modifier(textEdit.document()); + + RewriterView rewriterView{contextView->externalDependencies()}; + rewriterView.setCheckSemanticErrors(false); + rewriterView.setPossibleImportsEnabled(false); + rewriterView.setTextModifier(&modifier); + inputModel->setRewriterView(&rewriterView); + rewriterView.restoreAuxiliaryData(); + + if (rewriterView.errors().isEmpty() && rewriterView.rootModelNode().isValid()) { + try { + ModelMerger merger(contextView); + merger.insertModel(rewriterView.rootModelNode()); + } catch(Exception &/*e*/) { + qWarning() << "Cannot add model " << rewriterView.rootModelNode().displayName(); + return; + } + } + + // Merge the nodes in to the current document model + ModelPointer pasteModel = DesignDocumentView::pasteToModel(contextView->externalDependencies()); + QTC_ASSERT(pasteModel, return); + + DesignDocumentView view{contextView->externalDependencies()}; + pasteModel->attachView(&view); + QTC_ASSERT(view.rootModelNode().isValid(), return); + + pasteModel->detachView(&view); + contextView->model()->attachView(&view); + ModelNode originalNode = rewriterView.rootModelNode(); + view.executeInTransaction("DesignerActionManager::extractComponent", [=, &view]() { + // Acquire the root of selected node + const ModelNode rootOfSelection = selectedNode.parentProperty().parentModelNode(); + QTC_ASSERT(rootOfSelection.isValid(), return); + + ModelNode newNode = view.insertModel(originalNode); + rootOfSelection.defaultNodeListProperty().reparentHere(newNode); + + // Delete current selected node + QmlDesignerPlugin::instance()->currentDesignDocument()->deleteSelected(); + + // Set selection to inserted nodes + contextView->setSelectedModelNode(newNode); + }); +} + void add3DAssetToContentLibrary(const SelectionContext &selectionContext) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("ContentLibrary"); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 4d16549c95d..1cc0b50c149 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -100,6 +100,7 @@ void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState void removeLayout(const SelectionContext &selectionContext); void removePositioner(const SelectionContext &selectionContext); void moveToComponent(const SelectionContext &selectionContext); +void extractComponent(const SelectionContext &selectionContext); void add3DAssetToContentLibrary(const SelectionContext &selectionContext); PropertyName getIndexPropertyName(const ModelNode &modelNode); void addItemToStackedContainer(const SelectionContext &selectionContext);