/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "designdocument.h" #include "designdocumentview.h" #include "documentmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; enum { debug = false }; namespace QmlDesigner { /** \class QmlDesigner::DesignDocument DesignDocument acts as a facade to a model representing a qml document, and the different views/widgets accessing it. */ DesignDocument::DesignDocument(QObject *parent) : QObject(parent), m_documentModel(Model::create("QtQuick.Item", 1, 0)), m_subComponentManager(new SubComponentManager(m_documentModel.data(), this)), m_rewriterView (new RewriterView(RewriterView::Amend, m_documentModel.data())), m_documentLoaded(false), m_currentTarget(nullptr) { } DesignDocument::~DesignDocument() = default; Model *DesignDocument::currentModel() const { if (m_inFileComponentModel) return m_inFileComponentModel.data(); return m_documentModel.data(); } Model *DesignDocument::documentModel() const { return m_documentModel.data(); } QWidget *DesignDocument::centralWidget() const { return qobject_cast(parent()); } const ViewManager &DesignDocument::viewManager() const { return QmlDesignerPlugin::instance()->viewManager(); } ViewManager &DesignDocument::viewManager() { return QmlDesignerPlugin::instance()->viewManager(); } static ComponentTextModifier *createComponentTextModifier(TextModifier *originalModifier, RewriterView *rewriterView, const QString &componentText, const ModelNode &componentNode) { bool explicitComponent = componentText.contains(QLatin1String("Component")); ModelNode rootModelNode = rewriterView->rootModelNode(); int componentStartOffset; int componentEndOffset; int rootStartOffset = rewriterView->nodeOffset(rootModelNode); if (explicitComponent) { //the component is explciit we have to find the first definition inside componentStartOffset = rewriterView->firstDefinitionInsideOffset(componentNode); componentEndOffset = componentStartOffset + rewriterView->firstDefinitionInsideLength(componentNode); } else { //the component is implicit componentStartOffset = rewriterView->nodeOffset(componentNode); componentEndOffset = componentStartOffset + rewriterView->nodeLength(componentNode); } return new ComponentTextModifier(originalModifier, componentStartOffset, componentEndOffset, rootStartOffset); } bool DesignDocument::loadInFileComponent(const ModelNode &componentNode) { QString componentText = rewriterView()->extractText({componentNode}).value(componentNode); if (componentText.isEmpty()) return false; if (!componentNode.isRootNode()) { //change to subcomponent model changeToInFileComponentModel(createComponentTextModifier(m_documentTextModifier.data(), rewriterView(), componentText, componentNode)); } return true; } AbstractView *DesignDocument::view() const { return viewManager().nodeInstanceView(); } Model* DesignDocument::createInFileComponentModel() { Model *model = Model::create("QtQuick.Item", 1, 0); model->setFileUrl(m_documentModel->fileUrl()); return model; } QList DesignDocument::qmlParseWarnings() const { return m_rewriterView->warnings(); } bool DesignDocument::hasQmlParseWarnings() const { return !m_rewriterView->warnings().isEmpty(); } QList DesignDocument::qmlParseErrors() const { return m_rewriterView->errors(); } bool DesignDocument::hasQmlParseErrors() const { return !m_rewriterView->errors().isEmpty(); } QString DesignDocument::displayName() const { return fileName().toString(); } QString DesignDocument::simplfiedDisplayName() const { if (rootModelNode().id().isEmpty()) return rootModelNode().id(); else return rootModelNode().simplifiedTypeName(); } void DesignDocument::updateFileName(const Utils::FilePath & /*oldFileName*/, const Utils::FilePath &newFileName) { if (m_documentModel) m_documentModel->setFileUrl(QUrl::fromLocalFile(newFileName.toString())); if (m_inFileComponentModel) m_inFileComponentModel->setFileUrl(QUrl::fromLocalFile(newFileName.toString())); viewManager().setItemLibraryViewResourcePath(newFileName.toFileInfo().absolutePath()); emit displayNameChanged(displayName()); } Utils::FilePath DesignDocument::fileName() const { if (editor()) return editor()->document()->filePath(); return Utils::FilePath(); } ProjectExplorer::Target *DesignDocument::currentTarget() const { return m_currentTarget; } bool DesignDocument::isDocumentLoaded() const { return m_documentLoaded; } void DesignDocument::resetToDocumentModel() { m_inFileComponentModel.reset(); } void DesignDocument::loadDocument(QPlainTextEdit *edit) { Q_CHECK_PTR(edit); connect(edit, &QPlainTextEdit::undoAvailable, this, &DesignDocument::undoAvailable); connect(edit, &QPlainTextEdit::redoAvailable, this, &DesignDocument::redoAvailable); connect(edit, &QPlainTextEdit::modificationChanged, this, &DesignDocument::dirtyStateChanged); m_documentTextModifier.reset(new BaseTextEditModifier(dynamic_cast(plainTextEdit()))); connect(m_documentTextModifier.data(), &TextModifier::textChanged, this, &DesignDocument::updateQrcFiles); m_documentModel->setTextModifier(m_documentTextModifier.data()); m_inFileComponentTextModifier.reset(); updateFileName(Utils::FilePath(), fileName()); updateQrcFiles(); m_documentLoaded = true; } void DesignDocument::changeToDocumentModel() { viewManager().detachRewriterView(); viewManager().detachViewsExceptRewriterAndComponetView(); m_inFileComponentModel.reset(); viewManager().attachRewriterView(); viewManager().attachViewsExceptRewriterAndComponetView(); } void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textModifer) { m_inFileComponentTextModifier.reset(textModifer); viewManager().detachRewriterView(); viewManager().detachViewsExceptRewriterAndComponetView(); m_inFileComponentModel.reset(createInFileComponentModel()); m_inFileComponentModel->setTextModifier(m_inFileComponentTextModifier.data()); viewManager().attachRewriterView(); viewManager().attachViewsExceptRewriterAndComponetView(); } void DesignDocument::updateQrcFiles() { ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName()); if (currentProject) { for (const Utils::FilePath &fileName : currentProject->files(ProjectExplorer::Project::SourceFiles)) { if (fileName.endsWith(".qrc")) QmlJS::ModelManagerInterface::instance()->updateQrcFile(fileName.toString()); } } } void DesignDocument::changeToSubComponent(const ModelNode &componentNode) { if (QmlDesignerPlugin::instance()->currentDesignDocument() != this) return; if (m_inFileComponentModel) changeToDocumentModel(); bool subComponentLoaded = loadInFileComponent(componentNode); if (subComponentLoaded) attachRewriterToModel(); QmlDesignerPlugin::instance()->viewManager().pushInFileComponentOnCrumbleBar(componentNode); QmlDesignerPlugin::instance()->viewManager().setComponentNode(componentNode); } void DesignDocument::changeToMaster() { if (QmlDesignerPlugin::instance()->currentDesignDocument() != this) return; if (m_inFileComponentModel) changeToDocumentModel(); QmlDesignerPlugin::instance()->viewManager().pushFileOnCrumbleBar(fileName()); QmlDesignerPlugin::instance()->viewManager().setComponentNode(rootModelNode()); } void DesignDocument::attachRewriterToModel() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); Q_ASSERT(m_documentModel); viewManager().attachRewriterView(); Q_ASSERT(m_documentModel); QApplication::restoreOverrideCursor(); } bool DesignDocument::isUndoAvailable() const { if (plainTextEdit()) return plainTextEdit()->document()->isUndoAvailable(); return false; } bool DesignDocument::isRedoAvailable() const { if (plainTextEdit()) return plainTextEdit()->document()->isRedoAvailable(); return false; } void DesignDocument::close() { m_documentLoaded = false; emit designDocumentClosed(); } void DesignDocument::updateSubcomponentManager() { Q_ASSERT(m_subComponentManager); m_subComponentManager->update(QUrl::fromLocalFile(fileName().toString()), currentModel()->imports()); } void DesignDocument::deleteSelected() { if (!currentModel()) return; rewriterView()->executeInTransaction("DesignDocument::deleteSelected", [this](){ QList toDelete = view()->selectedModelNodes(); foreach (ModelNode node, toDelete) { if (node.isValid() && !node.isRootNode() && QmlObjectNode::isValidQmlObjectNode(node)) QmlObjectNode(node).destroy(); } }); } void DesignDocument::copySelected() { DesignDocumentView view; currentModel()->attachView(&view); DesignDocumentView::copyModelNodes(view.selectedModelNodes()); } void DesignDocument::cutSelected() { copySelected(); deleteSelected(); } static void scatterItem(const ModelNode &pastedNode, const ModelNode &targetNode, int offset = -2000) { if (targetNode.metaInfo().isValid() && targetNode.metaInfo().isLayoutable()) return; if (!(pastedNode.hasVariantProperty("x") && pastedNode.hasVariantProperty("y"))) return; bool scatter = false; foreach (const ModelNode &childNode, targetNode.directSubModelNodes()) { if ((childNode.variantProperty("x").value() == pastedNode.variantProperty("x").value()) && (childNode.variantProperty("y").value() == pastedNode.variantProperty("y").value())) scatter = true; } if (!scatter) return; if (offset == -2000) { double x = pastedNode.variantProperty("x").value().toDouble(); double y = pastedNode.variantProperty("y").value().toDouble(); double targetWidth = 20; double targetHeight = 20; x = x + double(qrand()) / RAND_MAX * targetWidth - targetWidth / 2; y = y + double(qrand()) / RAND_MAX * targetHeight - targetHeight / 2; pastedNode.variantProperty("x").setValue(int(x)); pastedNode.variantProperty("y").setValue(int(y)); } else { double x = pastedNode.variantProperty("x").value().toDouble(); double y = pastedNode.variantProperty("y").value().toDouble(); x = x + offset; y = y + offset; pastedNode.variantProperty("x").setValue(int(x)); pastedNode.variantProperty("y").setValue(int(y)); } } void DesignDocument::paste() { QScopedPointer pasteModel(DesignDocumentView::pasteToModel()); if (!pasteModel) return; DesignDocumentView view; pasteModel->attachView(&view); ModelNode rootNode(view.rootModelNode()); QList selectedNodes = rootNode.directSubModelNodes(); pasteModel->detachView(&view); if (rootNode.type() == "empty") return; if (rootNode.id() == "designer__Selection") { currentModel()->attachView(&view); ModelNode targetNode; if (!view.selectedModelNodes().isEmpty()) targetNode = view.selectedModelNodes().constFirst(); //In case we copy and paste a selection we paste in the parent item if ((view.selectedModelNodes().count() == selectedNodes.count()) && targetNode.isValid() && targetNode.hasParentProperty()) targetNode = targetNode.parentProperty().parentModelNode(); if (!targetNode.isValid()) targetNode = view.rootModelNode(); foreach (const ModelNode &node, selectedNodes) { foreach (const ModelNode &node2, selectedNodes) { if (node.isAncestorOf(node2)) selectedNodes.removeAll(node2); } } rewriterView()->executeInTransaction("DesignDocument::paste1", [this, &view, selectedNodes, targetNode](){ QList pastedNodeList; int offset = double(qrand()) / RAND_MAX * 20 - 10; foreach (const ModelNode &node, selectedNodes) { PropertyName defaultProperty(targetNode.metaInfo().defaultPropertyName()); ModelNode pastedNode(view.insertModel(node)); pastedNodeList.append(pastedNode); scatterItem(pastedNode, targetNode, offset); targetNode.nodeListProperty(defaultProperty).reparentHere(pastedNode); } view.setSelectedModelNodes(pastedNodeList); }); } else { rewriterView()->executeInTransaction("DesignDocument::paste1", [this, &view, selectedNodes, rootNode](){ currentModel()->attachView(&view); ModelNode pastedNode(view.insertModel(rootNode)); ModelNode targetNode; if (!view.selectedModelNodes().isEmpty()) targetNode = view.selectedModelNodes().constFirst(); if (!targetNode.isValid()) targetNode = view.rootModelNode(); if (targetNode.hasParentProperty() && (pastedNode.simplifiedTypeName() == targetNode.simplifiedTypeName()) && (pastedNode.variantProperty("width").value() == targetNode.variantProperty("width").value()) && (pastedNode.variantProperty("height").value() == targetNode.variantProperty("height").value())) targetNode = targetNode.parentProperty().parentModelNode(); PropertyName defaultProperty(targetNode.metaInfo().defaultPropertyName()); scatterItem(pastedNode, targetNode); if (targetNode.metaInfo().propertyIsListProperty(defaultProperty)) { targetNode.nodeListProperty(defaultProperty).reparentHere(pastedNode); } else { qWarning() << "Cannot reparent to" << targetNode; } view.setSelectedModelNodes({pastedNode}); }); NodeMetaInfo::clearCache(); } } void DesignDocument::selectAll() { if (!currentModel()) return; DesignDocumentView view; currentModel()->attachView(&view); QList allNodesExceptRootNode(view.allModelNodes()); allNodesExceptRootNode.removeOne(view.rootModelNode()); view.setSelectedModelNodes(allNodesExceptRootNode); } RewriterView *DesignDocument::rewriterView() const { return m_rewriterView.data(); } void DesignDocument::setEditor(Core::IEditor *editor) { m_textEditor = editor; // if the user closed the file explicit we do not want to do anything with it anymore connect(Core::EditorManager::instance(), &Core::EditorManager::aboutToSave, this, [this](Core::IDocument *document) { if (m_textEditor && m_textEditor->document() == document) { if (m_documentModel && m_documentModel->rewriterView()) m_documentModel->rewriterView()->writeAuxiliaryData(); } }); connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose, this, [this](Core::IEditor *editor) { if (m_textEditor.data() == editor) m_textEditor.clear(); }); connect(editor->document(), &Core::IDocument::filePathChanged, this, &DesignDocument::updateFileName); updateActiveTarget(); updateActiveTarget(); } Core::IEditor *DesignDocument::editor() const { return m_textEditor.data(); } TextEditor::BaseTextEditor *DesignDocument::textEditor() const { return qobject_cast(editor()); } QPlainTextEdit *DesignDocument::plainTextEdit() const { if (editor()) return qobject_cast(editor()->widget()); return nullptr; } ModelNode DesignDocument::rootModelNode() const { return rewriterView()->rootModelNode(); } void DesignDocument::undo() { if (rewriterView() && !rewriterView()->modificationGroupActive()) plainTextEdit()->undo(); viewManager().resetPropertyEditorView(); } void DesignDocument::redo() { if (rewriterView() && !rewriterView()->modificationGroupActive()) plainTextEdit()->redo(); viewManager().resetPropertyEditorView(); } static Target *getActiveTarget(DesignDocument *designDocument) { Project *currentProject = SessionManager::projectForFile(designDocument->fileName()); if (!currentProject) currentProject = ProjectTree::currentProject(); if (!currentProject) return nullptr; QObject::connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, designDocument, &DesignDocument::updateActiveTarget, Qt::UniqueConnection); QObject::connect(currentProject, &Project::activeTargetChanged, designDocument, &DesignDocument::updateActiveTarget, Qt::UniqueConnection); Target *target = currentProject->activeTarget(); if (!target || !target->kit()->isValid()) return nullptr; QObject::connect(target, &Target::kitChanged, designDocument, &DesignDocument::updateActiveTarget, Qt::UniqueConnection); return target; } void DesignDocument::updateActiveTarget() { m_currentTarget = getActiveTarget(this); viewManager().setNodeInstanceViewTarget(m_currentTarget); } void DesignDocument::contextHelp(const Core::IContext::HelpCallback &callback) const { if (view()) view()->contextHelp(callback); else callback({}); } } // namespace QmlDesigner