Files
qt-creator/src/plugins/qmldesigner/components/integration/designdocument.cpp
Miikka Heikkinen ede7969ea3 QmlDesigner: Make imported 3D scenes available via assets
A placeholder .q3d file is created under content for imported 3D
components found under Generated/QtQuick3D on asset view attach and
every time new import is done. .q3d file contains a project root
relative path to component's import folder.

.q3d files get generated preview as icon in assets view.

Imported 3D items are no longer shown in Components view.

Removing .q3d file will remove the corresponding module as well
as all model nodes created from that asset.

Removing last model node of asset will remove the import statement
on next document save.

Fixes: QDS-12193
Fixes: QDS-14565
Change-Id: If01546ca4c78334bac73b055ed156276f6f8f2a4
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
2025-02-13 09:22:06 +00:00

863 lines
27 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "designdocument.h"
#include "designdocumentview.h"
#include "documentmanager.h"
#include "qmldesignerconstants.h"
#include "qmlvisualnode.h"
#include <auxiliarydataproperties.h>
#ifndef QDS_USE_PROJECTSTORAGE
# include <metainfo.h>
#endif
#include <model/modelresourcemanagement.h>
#include <nodeinstanceview.h>
#include <nodelistproperty.h>
#include <rewritingexception.h>
#include <utils3d.h>
#include <variantproperty.h>
#include <viewmanager.h>
#include <qmldesignerplugin.h>
#include <qmlobjectnode.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/kit.h>
#include <qtsupport/qtkitaspect.h>
#include <qtsupport/qtsupportconstants.h>
#include <qtsupport/qtversionmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
#include <timelineactions.h>
#include <svgpasteaction.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/qtcassert.h>
#include <QFileInfo>
#include <QUrl>
#include <QDebug>
#include <QApplication>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QRandomGenerator>
#include <QClipboard>
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([[maybe_unused]] const QUrl &filePath,
ProjectStorageDependencies projectStorageDependencies,
ExternalDependenciesInterface &externalDependencies)
#ifdef QDS_USE_PROJECTSTORAGE
: m_documentModel(Model::create(projectStorageDependencies,
"Item",
{Import::createLibraryImport("QtQuick")},
filePath,
std::make_unique<ModelResourceManagement>()))
#else
: m_documentModel(
Model::create("QtQuick.Item", 1, 0, nullptr, std::make_unique<ModelResourceManagement>()))
, m_subComponentManager(new SubComponentManager(m_documentModel.get(), externalDependencies))
#endif
, m_rewriterView(new RewriterView(externalDependencies, RewriterView::Amend))
, m_documentLoaded(false)
, m_currentTarget(nullptr)
, m_projectStorageDependencies(projectStorageDependencies)
, m_externalDependencies{externalDependencies}
{
#ifndef QDS_USE_PROJECTSTORAGE
m_rewriterView->setIsDocumentRewriterView(true);
#endif
}
DesignDocument::~DesignDocument() = default;
Model *DesignDocument::currentModel() const
{
if (m_inFileComponentModel) {
return m_inFileComponentModel.get();
}
return m_documentModel.get();
}
Model *DesignDocument::documentModel() const
{
return m_documentModel.get();
}
QWidget *DesignDocument::centralWidget() const
{
return qobject_cast<QWidget*>(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.get(),
rewriterView(),
componentText,
componentNode));
}
return true;
}
const AbstractView *DesignDocument::view() const
{
return viewManager().view();
}
ModelPointer DesignDocument::createInFileComponentModel()
{
#ifdef QDS_USE_PROJECTSTORAGE
auto model = m_documentModel->createModel("Item", std::make_unique<ModelResourceManagement>());
#else
auto model = Model::create("QtQuick.Item",
1,
0,
nullptr,
std::make_unique<ModelResourceManagement>());
model->setFileUrl(m_documentModel->fileUrl());
model->setMetaInfo(m_documentModel->metaInfo());
#endif
return model;
}
bool DesignDocument::pasteSVG()
{
SVGPasteAction svgPasteAction;
if (!svgPasteAction.containsSVG(QApplication::clipboard()->text()))
return false;
rewriterView()->executeInTransaction("DesignDocument::paste1", [&]() {
ModelNode targetNode;
// If nodes are currently selected make the first node in selection the target
if (!view()->selectedModelNodes().isEmpty())
targetNode = view()->firstSelectedModelNode();
// If target is still invalid make the root node the target
if (!targetNode.isValid())
targetNode = view()->rootModelNode();
// Check if document has studio components import, if not create it
QmlDesigner::Import import = QmlDesigner::Import::createLibraryImport("QtQuick.Studio.Components", "1.0");
if (!currentModel()->hasImport(import, true, true)) {
QmlDesigner::Import studioComponentsImport = QmlDesigner::Import::createLibraryImport("QtQuick.Studio.Components", "1.0");
try {
currentModel()->changeImports({studioComponentsImport}, {});
} catch (const QmlDesigner::Exception &) {
QTC_ASSERT(false, return);
}
}
svgPasteAction.createQmlObjectNode(targetNode);
});
return true;
}
void DesignDocument::moveNodesToPosition(const QList<ModelNode> &nodes, const std::optional<QVector3D> &position)
{
if (!nodes.size())
return;
QList<ModelNode> movingNodes = nodes;
DesignDocumentView view{m_externalDependencies};
currentModel()->attachView(&view);
ModelNode targetNode; // the node that new nodes should be placed in
if (!view.selectedModelNodes().isEmpty())
targetNode = view.firstSelectedModelNode();
// in case we copy and paste a selection we paste in the parent item
if ((view.selectedModelNodes().size() == movingNodes.size()) && targetNode.hasParentProperty()) {
targetNode = targetNode.parentProperty().parentModelNode();
} else if (view.selectedModelNodes().isEmpty()) {
// if selection is empty and copied nodes are all 3D nodes, paste them under the active scene
bool all3DNodes = Utils::allOf(movingNodes, [](const ModelNode &node) {
return node.metaInfo().isQtQuick3DNode();
});
if (all3DNodes) {
int activeSceneId = Utils3D::active3DSceneId(m_documentModel.get());
if (activeSceneId != -1) {
NodeListProperty sceneNodeProperty = QmlVisualNode::findSceneNodeProperty(
rootModelNode().view(), activeSceneId);
targetNode = sceneNodeProperty.parentModelNode();
}
}
}
if (!targetNode.isValid())
targetNode = view.rootModelNode();
for (const ModelNode &node : std::as_const(movingNodes)) {
for (const ModelNode &node2 : std::as_const(movingNodes)) {
if (node.isAncestorOf(node2))
movingNodes.removeAll(node2);
}
}
bool isSingleNode = movingNodes.size() == 1;
if (isSingleNode) {
ModelNode singleNode = movingNodes.first();
if (targetNode.hasParentProperty()
&& singleNode.simplifiedTypeName() == targetNode.simplifiedTypeName()
&& singleNode.variantProperty("width").value() == targetNode.variantProperty("width").value()
&& singleNode.variantProperty("height").value() == targetNode.variantProperty("height").value()) {
targetNode = targetNode.parentProperty().parentModelNode();
}
}
const PropertyName defaultPropertyName = targetNode.metaInfo().defaultPropertyName();
if (!targetNode.metaInfo().property(defaultPropertyName).isListProperty())
qWarning() << Q_FUNC_INFO << "Cannot reparent to" << targetNode;
NodeListProperty parentProperty = targetNode.nodeListProperty(defaultPropertyName);
QList<ModelNode> pastedNodeList;
std::optional<QmlVisualNode> firstVisualNode;
QVector3D translationVect;
for (const ModelNode &node : std::as_const(movingNodes)) {
ModelNode pastedNode(view.insertModel(node));
pastedNodeList.append(pastedNode);
parentProperty.reparentHere(pastedNode);
QmlVisualNode visualNode(pastedNode);
if (!firstVisualNode && visualNode) {
firstVisualNode = visualNode;
translationVect = (position && firstVisualNode) ? *position - firstVisualNode->position()
: QVector3D();
}
visualNode.translate(translationVect);
}
view.setSelectedModelNodes(pastedNodeList);
view.model()->clearMetaInfoCache();
}
bool DesignDocument::inFileComponentModelActive() const
{
return bool(m_inFileComponentModel);
}
QList<DocumentMessage> DesignDocument::qmlParseWarnings() const
{
return m_rewriterView->warnings();
}
bool DesignDocument::hasQmlParseWarnings() const
{
return !m_rewriterView->warnings().isEmpty();
}
QList<DocumentMessage> 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()));
emit displayNameChanged(displayName());
}
Utils::FilePath DesignDocument::fileName() const
{
return editor() ? editor()->document()->filePath() : Utils::FilePath();
}
ProjectExplorer::Target *DesignDocument::currentTarget() const
{
return m_currentTarget;
}
bool DesignDocument::isDocumentLoaded() const
{
return m_documentLoaded;
}
void DesignDocument::resetToDocumentModel()
{
const QPlainTextEdit *edit = plainTextEdit();
if (edit)
edit->document()->clearUndoRedoStacks();
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(qobject_cast<TextEditor::TextEditorWidget *>(plainTextEdit())));
connect(m_documentTextModifier.get(),
&TextModifier::textChanged,
this,
&DesignDocument::updateQrcFiles);
m_rewriterView->setTextModifier(m_documentTextModifier.get());
m_inFileComponentTextModifier.reset();
updateFileName(Utils::FilePath(), fileName());
updateQrcFiles();
m_documentLoaded = true;
}
void DesignDocument::changeToDocumentModel()
{
viewManager().detachRewriterView();
viewManager().detachViewsExceptRewriterAndComponetView();
const QPlainTextEdit *edit = plainTextEdit();
if (edit)
edit->document()->clearUndoRedoStacks();
m_rewriterView->setTextModifier(m_documentTextModifier.get());
m_inFileComponentModel.reset();
m_inFileComponentTextModifier.reset();
viewManager().attachRewriterView();
viewManager().attachViewsExceptRewriterAndComponetView();
}
bool DesignDocument::isQtForMCUsProject() const
{
if (m_currentTarget)
return m_currentTarget->additionalData("CustomQtForMCUs").toBool();
return false;
}
Utils::FilePath DesignDocument::projectFolder() const
{
ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName());
if (currentProject)
return currentProject->projectDirectory();
return {};
}
bool DesignDocument::hasProject() const
{
return !DocumentManager::currentProjectDirPath().isEmpty();
}
void DesignDocument::setModified()
{
if (m_documentTextModifier)
m_documentTextModifier->textDocument()->setModified(true);
}
void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textModifer)
{
m_inFileComponentTextModifier.reset(textModifer);
viewManager().detachRewriterView();
viewManager().detachViewsExceptRewriterAndComponetView();
const QPlainTextEdit *edit = plainTextEdit();
if (edit)
edit->document()->clearUndoRedoStacks();
m_inFileComponentModel = createInFileComponentModel();
m_rewriterView->setTextModifier(m_inFileComponentTextModifier.get());
viewManager().attachRewriterView();
viewManager().attachViewsExceptRewriterAndComponetView();
}
void DesignDocument::updateQrcFiles()
{
ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName());
if (currentProject) {
const auto srcFiles = currentProject->files(ProjectExplorer::Project::SourceFiles);
for (const Utils::FilePath &fileName : srcFiles) {
if (fileName.endsWith(".qrc"))
QmlJS::ModelManagerInterface::instance()->updateQrcFile(fileName);
}
}
}
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::clearUndoRedoStacks() const
{
const QPlainTextEdit *edit = plainTextEdit();
if (edit)
edit->document()->clearUndoRedoStacks();
}
void DesignDocument::close()
{
m_documentLoaded = false;
emit designDocumentClosed();
}
#ifndef QDS_USE_PROJECTSTORAGE
void DesignDocument::updateSubcomponentManager()
{
Q_ASSERT(m_subComponentManager);
m_subComponentManager->update(QUrl::fromLocalFile(fileName().toString()),
currentModel()->imports() + currentModel()->possibleImports());
}
void DesignDocument::addSubcomponentManagerImport(const Import &import)
{
m_subComponentManager->addAndParseImport(import);
}
#endif
void DesignDocument::deleteSelected()
{
if (!currentModel())
return;
QStringList lockedNodes;
for (const ModelNode &modelNode : view()->selectedModelNodes()) {
for (const ModelNode &node : modelNode.allSubModelNodesAndThisNode()) {
if (node.isValid() && !node.isRootNode() && node.locked() && !lockedNodes.contains(node.id()))
lockedNodes.push_back(node.id());
}
}
if (!lockedNodes.empty()) {
Utils::sort(lockedNodes);
QString detailedText = QString("<b>" + tr("Locked items:") + "</b><br>");
for (const auto &id : std::as_const(lockedNodes))
detailedText.append("- " + id + "<br>");
detailedText.chop(QString("<br>").size());
QMessageBox msgBox;
msgBox.setTextFormat(Qt::RichText);
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle(tr("Delete/Cut Item"));
msgBox.setText(QString(tr("Deleting or cutting this item will modify locked items.") + "<br><br>%1")
.arg(detailedText));
msgBox.setInformativeText(tr("Do you want to continue by removing the item (Delete) or removing it and copying it to the clipboard (Cut)?"));
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
if (msgBox.exec() == QMessageBox::Cancel)
return;
}
rewriterView()->executeInTransaction("DesignDocument::deleteSelected", [this] {
const QList<ModelNode> toDelete = view()->selectedModelNodes();
for (ModelNode node : toDelete) {
if (node.isValid() && !node.isRootNode() && QmlObjectNode::isValidQmlObjectNode(node))
QmlObjectNode(node).destroy();
}
});
}
void DesignDocument::copySelected()
{
DesignDocumentView view{m_externalDependencies};
currentModel()->attachView(&view);
DesignDocumentView::copyModelNodes(view.selectedModelNodes(), m_externalDependencies);
}
void DesignDocument::cutSelected()
{
copySelected();
deleteSelected();
}
void DesignDocument::duplicateSelected()
{
DesignDocumentView view{m_externalDependencies};
currentModel()->attachView(&view);
const QList<ModelNode> selectedNodes = view.selectedModelNodes();
currentModel()->detachView(&view);
rewriterView()->executeInTransaction("DesignDocument::duplicateSelected", [this, selectedNodes]() {
moveNodesToPosition(selectedNodes, {});
});
}
void DesignDocument::paste()
{
pasteToPosition({});
}
void DesignDocument::pasteToPosition(const std::optional<QVector3D> &position)
{
if (pasteSVG())
return;
if (TimelineActions::clipboardContainsKeyframes()) // pasting keyframes is handled in TimelineView
return;
auto pasteModel = DesignDocumentView::pasteToModel(m_externalDependencies);
if (!pasteModel)
return;
DesignDocumentView view{m_externalDependencies};
pasteModel->attachView(&view);
ModelNode rootNode(view.rootModelNode());
if (rootNode.type() == "empty")
return;
QList<ModelNode> selectedNodes;
if (rootNode.id() == "__multi__selection__")
selectedNodes << rootNode.directSubModelNodes();
else
selectedNodes << rootNode;
pasteModel->detachView(&view);
rewriterView()->executeInTransaction("DesignDocument::pasteToPosition", [this, selectedNodes, position]() {
moveNodesToPosition(selectedNodes, position);
});
}
void DesignDocument::selectAll()
{
if (!currentModel())
return;
DesignDocumentView view{m_externalDependencies};
currentModel()->attachView(&view);
QList<ModelNode> allNodesExceptRootNode(view.allModelNodes());
allNodesExceptRootNode.removeOne(view.rootModelNode());
view.setSelectedModelNodes(allNodesExceptRootNode);
}
RewriterView *DesignDocument::rewriterView() const
{
return m_rewriterView.get();
}
#ifndef QDS_USE_PROJECTSTORAGE
static void removeUnusedImports(RewriterView *rewriter)
{
// Remove any import statements for asset based nodes (composed effect or imported3d)
// if there is no nodes using them in the scene.
QTC_ASSERT(rewriter && rewriter->model(), return);
GeneratedComponentUtils compUtils{rewriter->externalDependencies()};
const QString effectPrefix = compUtils.composedEffectsTypePrefix();
const QString imported3dPrefix = compUtils.import3dTypePrefix();
const QList<Utils::FilePath> qmlFiles = compUtils.imported3dComponents();
QHash<QString, QString> m_imported3dTypeMap;
for (const Utils::FilePath &qmlFile : qmlFiles) {
QString importName = compUtils.getImported3dImportName(qmlFile);
QString type = qmlFile.baseName();
m_imported3dTypeMap.insert(importName, type);
}
const Imports &imports = rewriter->model()->imports();
QHash<QString, Import> assetImports;
for (const Import &import : imports) {
if (import.url().startsWith(effectPrefix)) {
QString type = import.url().split('.').last();
assetImports.insert(type, import);
} else if (import.url().startsWith(imported3dPrefix)) {
assetImports.insert(m_imported3dTypeMap[import.url()], import);
}
}
const QList<ModelNode> allNodes = rewriter->allModelNodes();
for (const ModelNode &node : allNodes) {
if (QmlItemNode(node).isEffectItem()
|| (node.isComponent() && node.metaInfo().isQtQuick3DNode())) {
assetImports.remove(node.simplifiedTypeName());
}
}
if (!assetImports.isEmpty()) {
Imports removeImports;
for (const Import &import : assetImports)
removeImports.append(import);
rewriter->model()->changeImports({}, removeImports);
}
rewriter->forceAmend();
}
#endif
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()) {
#ifdef QDS_USE_PROJECTSTORAGE
// TODO: ProjectStorage should handle this via Model somehow (QDS-14519)
#else
removeUnusedImports(rewriterView());
#endif
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<TextEditor::BaseTextEditor*>(editor());
}
QPlainTextEdit *DesignDocument::plainTextEdit() const
{
if (editor())
return qobject_cast<QPlainTextEdit*>(editor()->widget());
return nullptr;
}
ModelNode DesignDocument::rootModelNode() const
{
return rewriterView()->rootModelNode();
}
void DesignDocument::undo()
{
if (rewriterView() && !rewriterView()->modificationGroupActive()) {
plainTextEdit()->undo();
rewriterView()->forceAmend();
}
viewManager().resetPropertyEditorView();
}
void DesignDocument::redo()
{
if (rewriterView() && !rewriterView()->modificationGroupActive()) {
plainTextEdit()->redo();
rewriterView()->forceAmend();
}
viewManager().resetPropertyEditorView();
}
static ProjectExplorer::Target *getActiveTarget(DesignDocument *designDocument)
{
auto currentProject = ProjectExplorer::ProjectManager::projectForFile(designDocument->fileName());
if (!currentProject)
currentProject = ProjectExplorer::ProjectTree::currentProject();
if (!currentProject)
return nullptr;
QObject::connect(ProjectExplorer::ProjectTree::instance(),
&ProjectExplorer::ProjectTree::currentProjectChanged,
designDocument,
&DesignDocument::updateActiveTarget,
Qt::UniqueConnection);
QObject::connect(currentProject,
&ProjectExplorer::Project::activeTargetChanged,
designDocument,
&DesignDocument::updateActiveTarget,
Qt::UniqueConnection);
auto target = currentProject->activeTarget();
if (!target || !target->kit()->isValid())
return nullptr;
QObject::connect(target,
&ProjectExplorer::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 (auto v = view())
QmlDesignerPlugin::contextHelp(callback, v->contextHelpId());
else
callback({});
}
} // namespace QmlDesigner