QmlDesigner: Simplify conditional import adding

Change-Id: I78e110a81f8117b711968b6be5b4241f96763c41
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Marco Bubke
2023-04-21 16:36:03 +02:00
parent 239d8882a9
commit dd5730d2a3
9 changed files with 184 additions and 121 deletions

View File

@@ -343,6 +343,8 @@ extend_qtc_library(QmlDesignerCore
modelnodepositionstorage.cpp modelnodepositionstorage.cpp
modeltotextmerger.cpp modeltotextmerger.cpp
modeltotextmerger.h modeltotextmerger.h
modelutils.cpp
modelutils.h
nodeabstractproperty.cpp nodeabstractproperty.cpp
nodelistproperty.cpp nodelistproperty.cpp
nodeproperty.cpp nodeproperty.cpp

View File

@@ -67,7 +67,7 @@ public:
void disableWidgets(); void disableWidgets();
void enableWidgets(); void enableWidgets();
void pushFileOnCrumbleBar(const Utils::FilePath &fileName); void pushFileOnCrumbleBar(const ::Utils::FilePath &fileName);
void pushInFileComponentOnCrumbleBar(const ModelNode &modelNode); void pushInFileComponentOnCrumbleBar(const ModelNode &modelNode);
void nextFileIsCalledInternally(); void nextFileIsCalledInternally();

View File

@@ -23,6 +23,8 @@
#include "seekerslider.h" #include "seekerslider.h"
#include "theme.h" #include "theme.h"
#include <model/modelutils.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagebox.h> #include <coreplugin/messagebox.h>
@@ -919,25 +921,12 @@ Edit3DBakeLightsAction *Edit3DView::bakeLightsAction() const
void Edit3DView::addQuick3DImport() void Edit3DView::addQuick3DImport()
{ {
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
if (document && !document->inFileComponentModelActive() && model()) { if (document && !document->inFileComponentModelActive() && model()
Import qtQuick3DImport; && Utils::addImportWithCheck(
differenceCall(model()->possibleImports(), model()->imports(), [&](const auto &import) { "QtQuick3D",
if (import.url() == "QtQuick3D") [](const Import &import) { return !import.hasVersion() || import.majorVersion() >= 6; },
qtQuick3DImport = import; model())) {
}); return;
if (!qtQuick3DImport.isEmpty()) {
if (!qtQuick3DImport.version().isEmpty() && qtQuick3DImport.majorVersion() >= 6) {
// Prefer empty version number in Qt6 and beyond
model()->changeImports({Import::createLibraryImport(qtQuick3DImport.url(),
{},
qtQuick3DImport.alias(),
qtQuick3DImport.importPaths())},
{});
} else {
model()->changeImports({qtQuick3DImport}, {});
}
return;
}
} }
Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"),
tr("Could not add QtQuick3D import to project.")); tr("Could not add QtQuick3D import to project."));

View File

@@ -14,6 +14,8 @@
#include "rewritingexception.h" #include "rewritingexception.h"
#include "viewmanager.h" #include "viewmanager.h"
#include <model/modelutils.h>
#include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
@@ -300,7 +302,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
if (exitVal == QDialog::Accepted) if (exitVal == QDialog::Accepted)
overwriteFiles = dlg.selectedFiles(); overwriteFiles = dlg.selectedFiles();
if (!overwriteFiles.isEmpty()) { if (!overwriteFiles.isEmpty()) {
overwriteFiles.append(Utils::toList(alwaysOverwrite)); overwriteFiles.append(::Utils::toList(alwaysOverwrite));
m_overwrittenImports.insert(pd.targetDirPath, overwriteFiles); m_overwrittenImports.insert(pd.targetDirPath, overwriteFiles);
} else { } else {
addWarning(tr("No files selected for overwrite, skipping import: \"%1\".").arg(pd.assetName)); addWarning(tr("No files selected for overwrite, skipping import: \"%1\".").arg(pd.assetName));
@@ -359,9 +361,8 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd)
qmlInfo.append("."); qmlInfo.append(".");
qmlInfo.append(pd.assetName); qmlInfo.append(pd.assetName);
qmlInfo.append('\n'); qmlInfo.append('\n');
m_requiredImports.append(Import::createLibraryImport( m_requiredImports.append(
QStringLiteral("%1.%2").arg(pd.targetDir.dirName(), QStringLiteral("%1.%2").arg(pd.targetDir.dirName(), pd.assetName));
pd.assetName), version));
while (qmlIt.hasNext()) { while (qmlIt.hasNext()) {
qmlIt.next(); qmlIt.next();
QFileInfo fi = QFileInfo(qmlIt.filePath()); QFileInfo fi = QFileInfo(qmlIt.filePath());
@@ -417,11 +418,8 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd)
} }
// Add quick3D import unless it is already added // Add quick3D import unless it is already added
if (impVersionMajor > 0 if (impVersionMajor > 0 && m_requiredImports.first() != "QtQuick3D")
&& m_requiredImports.first().url() != "QtQuick3D") { m_requiredImports.prepend("QtQuick3D");
m_requiredImports.prepend(Import::createLibraryImport(
"QtQuick3D", impVersionStr));
}
} }
if (impVersionMajor > 0 && impVersionMajor < 6) { if (impVersionMajor > 0 && impVersionMajor < 6) {
pd.iconFile = iconFileName; pd.iconFile = iconFileName;
@@ -683,66 +681,42 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport()
QFuture<void> result; QFuture<void> result;
if (modelManager) { if (modelManager) {
QmlJS::PathsAndLanguages pathToScan; QmlJS::PathsAndLanguages pathToScan;
pathToScan.maybeInsert(Utils::FilePath::fromString(m_importPath)); pathToScan.maybeInsert(::Utils::FilePath::fromString(m_importPath));
result = Utils::runAsync(&QmlJS::ModelManagerInterface::importScan, result = ::Utils::runAsync(&QmlJS::ModelManagerInterface::importScan,
modelManager->workingCopy(), pathToScan, modelManager->workingCopy(),
modelManager, true, true, true); pathToScan,
modelManager,
true,
true,
true);
} }
// First we have to wait a while to ensure qmljs detects new files and updates its // First we have to wait a while to ensure qmljs detects new files and updates its
// internal model. Then we make a non-change to the document to trigger qmljs snapshot // internal model. Then we force amend on rewriter to trigger qmljs snapshot update.
// update. There is an inbuilt delay before rewriter change actually updates the data
// model, so we need to wait for another moment to allow the change to take effect.
// Otherwise subsequent subcomponent manager update won't detect new imports properly.
QTimer *timer = new QTimer(parent()); QTimer *timer = new QTimer(parent());
static int counter; static int counter;
counter = 0; counter = 0;
timer->callOnTimeout([this, timer, progressTitle, model, result]() { timer->callOnTimeout([this, timer, progressTitle, model, result]() {
if (!isCancelled()) { if (!isCancelled()) {
notifyProgress(++counter, progressTitle); notifyProgress(++counter * 2, progressTitle);
if (counter < 50) { if (counter < 49) {
if (result.isCanceled() || result.isFinished()) if (result.isCanceled() || result.isFinished())
counter = 49; // skip to next step counter = 48; // skip to next step
} else if (counter == 50) { } else if (counter == 49) {
QmlDesignerPlugin::instance()->documentManager().resetPossibleImports(); QmlDesignerPlugin::instance()->documentManager().resetPossibleImports();
model->rewriterView()->textModifier()->replace(0, 0, {}); model->rewriterView()->forceAmend();
} else if (counter < 100) {
try { try {
const Imports posImports = model->possibleImports(); RewriterTransaction transaction = model->rewriterView()->beginRewriterTransaction(
const Imports currentImports = model->imports(); QByteArrayLiteral("ItemLibraryAssetImporter::finalizeQuick3DImport"));
Imports newImportsToAdd; bool success = Utils::addImportsWithCheck(m_requiredImports, model);
if (!success)
for (auto &imp : std::as_const(m_requiredImports)) {
const bool isPos = Utils::contains(posImports, [imp](const Import &posImp) {
return posImp.url() == imp.url();
});
const bool isCur = Utils::contains(currentImports, [imp](const Import &curImp) {
return curImp.url() == imp.url();
});
if (!(isPos || isCur))
return;
// Check again with 'contains' to ensure we insert latest version
if (!currentImports.contains(imp))
newImportsToAdd.append(imp);
}
if (counter == 99)
addError(tr("Failed to insert import statement into qml document.")); addError(tr("Failed to insert import statement into qml document."));
else transaction.commit();
counter = 99;
if (!newImportsToAdd.isEmpty()) {
RewriterTransaction transaction
= model->rewriterView()->beginRewriterTransaction(
QByteArrayLiteral("ItemLibraryAssetImporter::finalizeQuick3DImport"));
model->changeImports(newImportsToAdd, {});
transaction.commit();
}
} catch (const RewritingException &e) { } catch (const RewritingException &e) {
addError(tr("Failed to update imports: %1").arg(e.description())); addError(tr("Failed to update imports: %1").arg(e.description()));
counter = 99;
} }
} else if (counter >= 100) { } else if (counter >= 50) {
if (!m_overwrittenImports.isEmpty()) if (!m_overwrittenImports.isEmpty())
model->rewriterView()->emitCustomNotification("asset_import_update"); model->rewriterView()->emitCustomNotification("asset_import_update");
timer->stop(); timer->stop();
@@ -752,7 +726,7 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport()
timer->stop(); timer->stop();
} }
}); });
timer->start(50); timer->start(100);
} else { } else {
notifyFinished(); notifyFinished();
} }

View File

@@ -107,7 +107,7 @@ private:
int m_currentImportId = 0; int m_currentImportId = 0;
QHash<int, ParseData> m_parseData; QHash<int, ParseData> m_parseData;
QString m_progressTitle; QString m_progressTitle;
Imports m_requiredImports; QStringList m_requiredImports;
QList<int> m_puppetQueue; QList<int> m_puppetQueue;
}; };
} // QmlDesigner } // QmlDesigner

View File

@@ -18,6 +18,7 @@
#include <itemlibrarymodel.h> #include <itemlibrarymodel.h>
#include <metainfo.h> #include <metainfo.h>
#include <model.h> #include <model.h>
#include <model/modelutils.h>
#include <rewritingexception.h> #include <rewritingexception.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
@@ -59,7 +60,7 @@ namespace QmlDesigner {
static QString propertyEditorResourcesPath() static QString propertyEditorResourcesPath()
{ {
#ifdef SHARE_QML_PATH #ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) if (::Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
#endif #endif
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
@@ -80,26 +81,16 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event)
ItemLibraryEntry entry = m_itemToDrag.value<ItemLibraryEntry>(); ItemLibraryEntry entry = m_itemToDrag.value<ItemLibraryEntry>();
// For drag to be handled correctly, we must have the component properly imported // For drag to be handled correctly, we must have the component properly imported
// beforehand, so we import the module immediately when the drag starts // beforehand, so we import the module immediately when the drag starts
if (!entry.requiredImport().isEmpty()) { if (!entry.requiredImport().isEmpty()
// We don't know if required import is library of file import, so try both. && !Utils::addImportWithCheck(entry.requiredImport(), m_model)) {
Import libImport = Import::createLibraryImport(entry.requiredImport()); qWarning() << __FUNCTION__ << "Required import adding failed:"
Import fileImport = Import::createFileImport(entry.requiredImport()); << entry.requiredImport();
if (!m_model->hasImport(libImport, true, true)
&& !m_model->hasImport(fileImport, true, true)) {
const Imports possImports = m_model->possibleImports();
for (const auto &possImport : possImports) {
if ((!possImport.url().isEmpty() && possImport.url() == libImport.url())
|| (!possImport.file().isEmpty() && possImport.file() == fileImport.file())) {
m_model->changeImports({possImport}, {});
break;
}
}
}
} }
if (model) { if (model) {
model->startDrag(m_itemLibraryModel->getMimeData(entry), model->startDrag(m_itemLibraryModel->getMimeData(entry),
Utils::StyleHelper::dpiSpecificImageFile(entry.libraryEntryIconPath())); ::Utils::StyleHelper::dpiSpecificImageFile(
entry.libraryEntryIconPath()));
} }
m_itemToDrag = {}; m_itemToDrag = {};
@@ -154,7 +145,7 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache)
updateSearch(); updateSearch();
setStyleSheet(Theme::replaceCssColors( setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); QString::fromUtf8(::Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F5), this); m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F5), this);
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &ItemLibraryWidget::reloadQmlSource); connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &ItemLibraryWidget::reloadQmlSource);
@@ -176,10 +167,9 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache)
{"itemLibraryIconHeight", m_itemIconSize.height()}, {"itemLibraryIconHeight", m_itemIconSize.height()},
{"rootView", QVariant::fromValue(this)}, {"rootView", QVariant::fromValue(this)},
{"widthLimit", HORIZONTAL_LAYOUT_WIDTH_LIMIT}, {"widthLimit", HORIZONTAL_LAYOUT_WIDTH_LIMIT},
{"highlightColor", Utils::StyleHelper::notTooBrightHighlightColor()}, {"highlightColor", ::Utils::StyleHelper::notTooBrightHighlightColor()},
{"tooltipBackend", QVariant::fromValue(m_previewTooltipBackend.get())}}); {"tooltipBackend", QVariant::fromValue(m_previewTooltipBackend.get())}});
reloadQmlSource(); reloadQmlSource();
} }
@@ -303,7 +293,7 @@ void ItemLibraryWidget::setModel(Model *model)
QString ItemLibraryWidget::qmlSourcesPath() QString ItemLibraryWidget::qmlSourcesPath()
{ {
#ifdef SHARE_QML_PATH #ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) if (::Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/itemLibraryQmlSources"; return QLatin1String(SHARE_QML_PATH) + "/itemLibraryQmlSources";
#endif #endif
return Core::ICore::resourcePath("qmldesigner/itemLibraryQmlSources").toString(); return Core::ICore::resourcePath("qmldesigner/itemLibraryQmlSources").toString();

View File

@@ -9,23 +9,24 @@
#include "qmldesignerplugin.h" #include "qmldesignerplugin.h"
#include "assetslibrarywidget.h" #include "assetslibrarywidget.h"
#include <abstractview.h>
#include <bindingproperty.h> #include <bindingproperty.h>
#include <coreplugin/icore.h>
#include <designeractionmanager.h>
#include <designersettings.h> #include <designersettings.h>
#include <import.h>
#include <invalididexception.h>
#include <materialutils.h>
#include <metainfo.h>
#include <model/modelutils.h>
#include <nodeabstractproperty.h> #include <nodeabstractproperty.h>
#include <nodehints.h> #include <nodehints.h>
#include <nodelistproperty.h> #include <nodelistproperty.h>
#include <nodeproperty.h> #include <nodeproperty.h>
#include <variantproperty.h>
#include <metainfo.h>
#include <materialutils.h>
#include <abstractview.h>
#include <invalididexception.h>
#include <rewritingexception.h> #include <rewritingexception.h>
#include <variantproperty.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmlitemnode.h> #include <qmlitemnode.h>
#include <designeractionmanager.h>
#include <import.h>
#include <coreplugin/icore.h>
#include <qmlprojectmanager/qmlproject.h> #include <qmlprojectmanager/qmlproject.h>
@@ -210,7 +211,7 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const
return modelNode.displayName(); return modelNode.displayName();
} else if (role == Qt::DecorationRole) { } else if (role == Qt::DecorationRole) {
if (currentQmlObjectNode.hasError()) if (currentQmlObjectNode.hasError())
return Utils::Icons::WARNING.icon(); return ::Utils::Icons::WARNING.icon();
return modelNode.typeIcon(); return modelNode.typeIcon();
@@ -314,14 +315,14 @@ QList<ModelNode> NavigatorTreeModel::filteredList(const NodeListProperty &proper
if (m_nameFilter.isEmpty()) { if (m_nameFilter.isEmpty()) {
nameFilteredList = propertyNodes; nameFilteredList = propertyNodes;
} else { } else {
nameFilteredList.append(Utils::filtered(propertyNodes, [&] (const ModelNode &arg){ nameFilteredList.append(::Utils::filtered(propertyNodes, [&](const ModelNode &arg) {
const bool value = m_nameFilteredList.contains(arg); const bool value = m_nameFilteredList.contains(arg);
return value; return value;
})); }));
} }
if (filter) { if (filter) {
list.append(Utils::filtered(nameFilteredList, [] (const ModelNode &arg) { list.append(::Utils::filtered(nameFilteredList, [](const ModelNode &arg) {
const bool value = (QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator()) const bool value = (QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator())
&& arg.id() != Constants::MATERIAL_LIB_ID; && arg.id() != Constants::MATERIAL_LIB_ID;
return value; return value;
@@ -899,18 +900,8 @@ ModelNode NavigatorTreeModel::handleItemLibraryFontDrop(const QString &fontFamil
void NavigatorTreeModel::addImport(const QString &importName) void NavigatorTreeModel::addImport(const QString &importName)
{ {
Import import = Import::createLibraryImport(importName); if (!Utils::addImportWithCheck(importName, m_view->model()))
if (!m_view->model()->hasImport(import, true, true)) { qWarning() << __FUNCTION__ << "Adding import failed:" << importName;
const Imports possImports = difference(m_view->model()->possibleImports(),
m_view->model()->imports());
for (const auto &possImport : possImports) {
if (possImport.url() == import.url()) {
import = possImport;
m_view->model()->changeImports({import}, {});
break;
}
}
}
} }
bool QmlDesigner::NavigatorTreeModel::moveNodeToParent(const NodeAbstractProperty &targetProperty, bool QmlDesigner::NavigatorTreeModel::moveNodeToParent(const NodeAbstractProperty &targetProperty,
@@ -1269,12 +1260,12 @@ static QList<ModelNode> collectParents(const QList<ModelNode> &modelNodes)
} }
} }
return Utils::toList(parents); return ::Utils::toList(parents);
} }
QList<QPersistentModelIndex> NavigatorTreeModel::nodesToPersistentIndex(const QList<ModelNode> &modelNodes) QList<QPersistentModelIndex> NavigatorTreeModel::nodesToPersistentIndex(const QList<ModelNode> &modelNodes)
{ {
return Utils::transform(modelNodes, [this](const ModelNode &modelNode) { return ::Utils::transform(modelNodes, [this](const ModelNode &modelNode) {
return QPersistentModelIndex(indexForModelNode(modelNode)); return QPersistentModelIndex(indexForModelNode(modelNode));
}); });
} }

View File

@@ -0,0 +1,93 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "modelutils.h"
#include <utils/expected.h>
#include <algorithm>
namespace QmlDesigner::Utils {
namespace {
enum class ImportError { EmptyImportName, HasAlreadyImport, NoModule };
::Utils::expected<Import, ImportError> findImport(const QString &importName,
const std::function<bool(const Import &)> &predicate,
const Imports &imports,
const Imports &modules)
{
if (importName.isEmpty())
return ::Utils::make_unexpected(ImportError::EmptyImportName);
auto hasName = [&](const auto &import) {
return import.url() == importName || import.file() == importName;
};
bool hasImport = std::any_of(imports.begin(), imports.end(), hasName);
if (hasImport)
return ::Utils::make_unexpected(ImportError::HasAlreadyImport);
auto foundModule = std::find_if(modules.begin(), modules.end(), [&](const Import &import) {
return hasName(import) && predicate(import);
});
if (foundModule == modules.end())
return ::Utils::make_unexpected(ImportError::NoModule);
return *foundModule;
}
} // namespace
bool addImportWithCheck(const QString &importName,
const std::function<bool(const Import &)> &predicate,
Model *model)
{
return addImportsWithCheck({importName}, predicate, model);
}
bool addImportWithCheck(const QString &importName, Model *model)
{
return addImportWithCheck(
importName, [](const Import &) { return true; }, model);
}
bool addImportsWithCheck(const QStringList &importNames, Model *model)
{
return addImportsWithCheck(
importNames, [](const Import &) { return true; }, model);
}
bool addImportsWithCheck(const QStringList &importNames,
const std::function<bool(const Import &)> &predicate,
Model *model)
{
const Imports &imports = model->imports();
const Imports &modules = model->possibleImports();
Imports importsToAdd;
importsToAdd.reserve(importNames.size());
for (const QString &importName : importNames) {
auto import = findImport(importName, predicate, imports, modules);
if (import) {
importsToAdd.push_back(*import);
} else {
if (import.error() == ImportError::NoModule)
return false;
else
continue;
}
}
if (!importsToAdd.isEmpty())
model->changeImports(std::move(importsToAdd), {});
return true;
}
} // namespace QmlDesigner::Utils

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmldesignercorelib_global.h"
#include <import.h>
#include <model.h>
#include <functional>
namespace QmlDesigner::Utils {
QMLDESIGNERCORE_EXPORT bool addImportsWithCheck(const QStringList &importNames,
const std::function<bool(const Import &)> &predicate,
Model *model);
QMLDESIGNERCORE_EXPORT bool addImportsWithCheck(const QStringList &importNames, Model *model);
QMLDESIGNERCORE_EXPORT bool addImportWithCheck(const QString &importName,
const std::function<bool(const Import &)> &predicate,
Model *model);
QMLDESIGNERCORE_EXPORT bool addImportWithCheck(const QString &importName, Model *model);
} // namespace QmlDesigner::Utils