diff --git a/dist/changes-4.10.1.md b/dist/changes-4.10.1.md index 6e5d0db956d..7d942ba68ab 100644 --- a/dist/changes-4.10.1.md +++ b/dist/changes-4.10.1.md @@ -19,9 +19,13 @@ you can check out from the public Git repository. For example: * Fixed that text moved around when resizing and zooming (QTCREATORBUG-4756) +## All Projects + +* Fixed `Qt Creator Plugin` wizard (QTCREATORBUG-22945) + ## Debugging -* Fixed more layout restoration issues (QTCREATORBUG-22286, QTCREATORBUG-22938) +* Fixed more layout restoration issues (QTCREATORBUG-22286, QTCREATORBUG-22415, QTCREATORBUG-22938) ### LLDB @@ -36,7 +40,13 @@ you can check out from the public Git repository. For example: ### macOS +* Fixed debugging with Xcode 11 (QTCREATORBUG-22955) * Fixed window stacking order after closing file dialog (QTCREATORBUG-22906) +* Fixed window size after exiting fullscreen + +### QNX + +* Fixed that QNX compiler could not be selected for C ## Credits for these changes go to: @@ -44,7 +54,9 @@ Aleksei German Alexander Akulich Andre Hartmann André Pönitz +Christian Kandeler Christian Stenger +Cristian Adam David Schulz Eike Ziller Knud Dollereder @@ -53,5 +65,5 @@ Lisandro Damián Nicanor Pérez Meyer Nikolai Kosjar Orgad Shaneh Richard Weickelt +Sergey Belyashov Thomas Hartmann - diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index 2f056c7a932..027560f333a 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -136,8 +136,6 @@ class PlainDumper: printer = self.printer.gen_printer(value.nativeValue) except: printer = self.printer.invoke(value.nativeValue) - lister = getattr(printer, 'children', None) - children = [] if lister is None else list(lister()) d.putType(value.nativeValue.type.name) val = printer.to_string() if isinstance(val, str): @@ -149,11 +147,20 @@ class PlainDumper: d.putCharArrayValue(val.address, val.length, val.type.target().sizeof) - d.putNumChild(len(children)) - if d.isExpanded(): - with Children(d): - for child in children: - d.putSubItem(child[0], d.fromNativeValue(gdb.Value(child[1]))) + lister = getattr(printer, 'children', None) + if lister is None: + d.putNumChild(0) + else: + if d.isExpanded(): + children = lister() + with Children(d): + i = 0 + for (name, child) in children: + d.putSubItem(name, d.fromNativeValue(child)) + i += 1 + if i > 1000: + break + d.putNumChild(1) def importPlainDumpers(args): if args == 'off': diff --git a/src/libs/languageutils/fakemetaobject.cpp b/src/libs/languageutils/fakemetaobject.cpp index 1a6579b6e33..3d8866972b4 100644 --- a/src/libs/languageutils/fakemetaobject.cpp +++ b/src/libs/languageutils/fakemetaobject.cpp @@ -44,8 +44,8 @@ QString FakeMetaEnum::name() const void FakeMetaEnum::setName(const QString &name) { m_name = name; } -void FakeMetaEnum::addKey(const QString &key, int value) -{ m_keys.append(key); m_values.append(value); } +void FakeMetaEnum::addKey(const QString &key) +{ m_keys.append(key); } QString FakeMetaEnum::key(int index) const { return m_keys.at(index); } @@ -71,10 +71,6 @@ void FakeMetaEnum::addToHash(QCryptographicHash &hash) const hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); } - len = m_values.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - foreach (int value, m_values) - hash.addData(reinterpret_cast(&value), sizeof(value)); } QString FakeMetaEnum::describe(int baseIndent) const @@ -82,16 +78,14 @@ QString FakeMetaEnum::describe(int baseIndent) const QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); QString res = QLatin1String("Enum "); res += name(); - res += QLatin1String(":{"); + res += QLatin1String(": ["); for (int i = 0; i < keyCount(); ++i) { res += newLine; res += QLatin1String(" "); res += key(i); - res += QLatin1String(": "); - res += QString::number(m_values.value(i, -1)); } res += newLine; - res += QLatin1Char('}'); + res += QLatin1Char(']'); return res; } diff --git a/src/libs/languageutils/fakemetaobject.h b/src/libs/languageutils/fakemetaobject.h index 543d80b45c9..a629ebedf8f 100644 --- a/src/libs/languageutils/fakemetaobject.h +++ b/src/libs/languageutils/fakemetaobject.h @@ -43,7 +43,6 @@ namespace LanguageUtils { class LANGUAGEUTILS_EXPORT FakeMetaEnum { QString m_name; QStringList m_keys; - QList m_values; public: FakeMetaEnum(); @@ -54,7 +53,7 @@ public: QString name() const; void setName(const QString &name); - void addKey(const QString &key, int value); + void addKey(const QString &key); QString key(int index) const; int keyCount() const; QStringList keys() const; diff --git a/src/libs/qmljs/qmljsfindexportedcpptypes.cpp b/src/libs/qmljs/qmljsfindexportedcpptypes.cpp index 7ba890e17a2..7b3749ab519 100644 --- a/src/libs/qmljs/qmljsfindexportedcpptypes.cpp +++ b/src/libs/qmljs/qmljsfindexportedcpptypes.cpp @@ -737,7 +737,7 @@ static LanguageUtils::FakeMetaObject::Ptr buildFakeMetaObject( Symbol *enumMember = e->memberAt(j); if (!enumMember->name()) continue; - metaEnum.addKey(namePrinter.prettyName(enumMember->name()), 0); + metaEnum.addKey(namePrinter.prettyName(enumMember->name())); } fmo->addEnum(metaEnum); } diff --git a/src/libs/qmljs/qmljstypedescriptionreader.cpp b/src/libs/qmljs/qmljstypedescriptionreader.cpp index 895922a1eb8..152432ca7be 100644 --- a/src/libs/qmljs/qmljstypedescriptionreader.cpp +++ b/src/libs/qmljs/qmljstypedescriptionreader.cpp @@ -649,39 +649,34 @@ void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUt return; } - ExpressionStatement *expStmt = AST::cast(ast->statement); + auto *expStmt = AST::cast(ast->statement); if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected object literal after colon.")); + addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); return; } - ObjectPattern *objectLit = AST::cast(expStmt->expression); - if (!objectLit) { - addError(expStmt->firstSourceLocation(), tr("Expected object literal after colon.")); - return; - } - - for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { - PatternProperty *assignement = AST::cast(it->property); - if (assignement) { - StringLiteralPropertyName *propName = AST::cast(assignement->name); - NumericLiteral *value = AST::cast(assignement->initializer); - UnaryMinusExpression *minus = AST::cast(assignement->initializer); - if (minus) - value = AST::cast(minus->expression); - if (!propName || !value) { - addError(objectLit->firstSourceLocation(), tr("Expected object literal to contain only 'string: number' elements.")); - continue; + if (auto *objectLit = AST::cast(expStmt->expression)) { + for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { + if (PatternProperty *assignement = it->property) { + if (auto *name = AST::cast(assignement->name)) { + fme->addKey(name->id.toString()); + continue; + } } - - double v = value->value; - if (minus) - v = -v; - fme->addKey(propName->id.toString(), v); - continue; + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); } - PatternPropertyList *getterSetter = AST::cast(it->next); - if (getterSetter) - addError(objectLit->firstSourceLocation(), tr("Enum should not contain getter and setters, but only 'string: number' elements.")); + } else if (auto *arrayLit = AST::cast(expStmt->expression)) { + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + if (PatternElement *element = it->element) { + if (auto *name = AST::cast(element->initializer)) { + fme->addKey(name->value.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else { + addError(ast->statement->firstSourceLocation(), + tr("Expected either array or object literal as enum definition.")); } } diff --git a/src/plugins/baremetal/sdcctoolchain.cpp b/src/plugins/baremetal/sdcctoolchain.cpp index 199d8ef0aa6..a7b62686c8e 100644 --- a/src/plugins/baremetal/sdcctoolchain.cpp +++ b/src/plugins/baremetal/sdcctoolchain.cpp @@ -381,27 +381,41 @@ QList SdccToolChainFactory::autoDetect(const QList &al if (Utils::HostOsInfo::isWindowsHost()) { -#ifdef Q_OS_WIN64 - static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\SDCC"; -#else - static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\SDCC"; -#endif - - QSettings registry(kRegistryNode, QSettings::NativeFormat); - QString compilerPath = registry.value("Default").toString(); - if (!compilerPath.isEmpty()) { + // Tries to detect the candidate from the 32-bit + // or 64-bit system registry format. + auto probeCandidate = [](QSettings::Format format) { + QSettings registry("HKEY_LOCAL_MACHINE\\SOFTWARE\\SDCC", + format); + QString compilerPath = registry.value("Default").toString(); + if (compilerPath.isEmpty()) + return Candidate{}; // Build full compiler path. compilerPath += "\\bin\\sdcc.exe"; const FilePath fn = FilePath::fromString( QFileInfo(compilerPath).absoluteFilePath()); - if (compilerExists(fn)) { - // Build compiler version. - const QString version = QString("%1.%2.%3").arg( - registry.value("VersionMajor").toString(), - registry.value("VersionMinor").toString(), - registry.value("VersionRevision").toString()); - candidates.push_back({fn, version}); - } + if (!compilerExists(fn)) + return Candidate{}; + // Build compiler version. + const QString version = QString("%1.%2.%3").arg( + registry.value("VersionMajor").toString(), + registry.value("VersionMinor").toString(), + registry.value("VersionRevision").toString()); + return Candidate{fn, version}; + }; + + const QSettings::Format allowedFormats[] = { + QSettings::NativeFormat, +#ifdef Q_OS_WIN + QSettings::Registry32Format, + QSettings::Registry64Format +#endif + }; + + for (const QSettings::Format format : allowedFormats) { + const auto candidate = probeCandidate(format); + if (candidate.compilerPath.isEmpty() || candidates.contains(candidate)) + continue; + candidates.push_back(candidate); } } diff --git a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp index c033b6b1019..7e973d86e18 100644 --- a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp +++ b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp @@ -33,8 +33,9 @@ #include #include -#include #include +#include +#include #include using namespace ProjectExplorer; @@ -51,6 +52,11 @@ QdbDeployConfigurationFactory::QdbDeployConfigurationFactory() "Deploy to Boot2Qt target")); setUseDeploymentDataView(); + addInitialStep(RemoteLinux::MakeInstallStep::stepId(), [](Target *target) { + const Project * const prj = target->project(); + return prj->deploymentKnowledge() == DeploymentKnowledge::Bad + && prj->hasMakeInstallEquivalent(); + }); addInitialStep(RemoteLinuxCheckForFreeDiskSpaceStep::stepId()); addInitialStep(QdbStopApplicationStep::stepId()); addInitialStep(GenericDirectUploadStep::stepId()); diff --git a/src/plugins/boot2qt/qdbplugin.cpp b/src/plugins/boot2qt/qdbplugin.cpp index 8a07dbba6c7..81a2fc80d05 100644 --- a/src/plugins/boot2qt/qdbplugin.cpp +++ b/src/plugins/boot2qt/qdbplugin.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -175,8 +176,8 @@ public: QdbDeployStepFactory m_checkForFreeDiskSpaceStepFactory; - QdbDeployStepFactory - m_directUploadStepFactory; + QdbDeployStepFactory m_directUploadStepFactory; + QdbDeployStepFactory m_makeInstallStepFactory; const QList supportedRunConfigs { m_runConfigFactory.id(), diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 9d59cc0fe07..8fa02ef988d 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -2285,8 +2285,8 @@ void DebuggerEngine::openDisassemblerView(const Location &location) void DebuggerEngine::raiseWatchersWindow() { - if (d->m_watchersView) { - if (auto dock = qobject_cast(d->m_watchersView->parentWidget())) { + if (d->m_watchersView && d->m_watchersWindow) { + if (auto dock = qobject_cast(d->m_watchersWindow->parentWidget())) { if (QAction *act = dock->toggleViewAction()) { if (!act->isChecked()) QTimer::singleShot(1, act, [act] { act->trigger(); }); diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index 434ec9e1cc1..8b0c3b84542 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -45,11 +45,13 @@ #include #include +#include #include #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -58,6 +60,7 @@ namespace Python { namespace Internal { static constexpr char startPylsInfoBarId[] = "PythonEditor::StartPyls"; +static constexpr char installPylsInfoBarId[] = "PythonEditor::InstallPyls"; struct PythonForProject { @@ -193,6 +196,90 @@ static LanguageClient::Client *registerLanguageServer(const PythonForProject &py return LanguageClient::LanguageClientManager::clientForSetting(settings).value(0); } +class PythonLSInstallHelper : public QObject +{ + Q_OBJECT +public: + PythonLSInstallHelper(const PythonForProject &python, QPointer document) + : m_python(python) + , m_document(document) + {} + + void run() + { + auto killTimer = new QTimer(&m_process); + + connect(&m_process, + QOverload::of(&QProcess::finished), + this, + &PythonLSInstallHelper::installFinished); + connect(&m_process, + &QProcess::readyReadStandardError, + this, + &PythonLSInstallHelper::errorAvailable); + connect(&m_process, + &QProcess::readyReadStandardOutput, + this, + &PythonLSInstallHelper::outputAvailable); + connect(killTimer, &QTimer::timeout, [this]() { + SynchronousProcess::stopProcess(m_process); + Core::MessageManager::write(tr("The Python language server installation timed out.")); + }); + + // on windows the pyls 0.28.3 crashes with pylint so just install the pyflakes linter + const QString &pylsVersion = HostOsInfo::isWindowsHost() + ? QString{"python-language-server[pyflakes]"} + : QString{"python-language-server[all]"}; + + m_process.start(m_python.path.toString(), + {"-m", "pip", "install", pylsVersion}); + + Core::MessageManager::write(tr("Running '%1 %2' to install python language server") + .arg(m_process.program(), m_process.arguments().join(' '))); + + killTimer->start(5 /*minutes*/ * 60 * 1000); + } + +private: + void installFinished(int exitCode, QProcess::ExitStatus exitStatus) + { + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + if (LanguageClient::Client *client = registerLanguageServer(m_python)) + LanguageClient::LanguageClientManager::reOpenDocumentWithClient(m_document, client); + } else { + Core::MessageManager::write( + tr("Installing the Python language server failed with exit code %1").arg(exitCode)); + } + deleteLater(); + } + void outputAvailable() + { + const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed()); + if (!stdOut.isEmpty()) + Core::MessageManager::write(stdOut); + } + + void errorAvailable() + { + const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed()); + if (!stdErr.isEmpty()) + Core::MessageManager::write(stdErr); + } + + QProcess m_process; + const PythonForProject m_python; + QPointer m_document; +}; + +static void installPythonLanguageServer(const PythonForProject &python, + QPointer document) +{ + document->infoBar()->removeInfo(installPylsInfoBarId); + + auto install = new PythonLSInstallHelper(python, document); + install->run(); +} + static void setupPythonLanguageServer(const PythonForProject &python, QPointer document) { @@ -206,13 +293,25 @@ static void updateEditorInfoBar(const PythonForProject &python, TextEditor::Text const PythonLanguageServerState &lsState = checkPythonLanguageServer(python.path, document); if (lsState.state == PythonLanguageServerState::CanNotBeInstalled - || lsState.state == PythonLanguageServerState::AlreadyConfigured - || lsState.state == PythonLanguageServerState::CanBeInstalled /* TODO */) { + || lsState.state == PythonLanguageServerState::AlreadyConfigured) { return; } Core::InfoBar *infoBar = document->infoBar(); - if (lsState.state == PythonLanguageServerState::AlreadyInstalled + if (lsState.state == PythonLanguageServerState::CanBeInstalled + && infoBar->canInfoBeAdded(installPylsInfoBarId)) { + auto message + = PythonEditorFactory::tr( + "Install and set up Python language server for %1 (%2). " + "The language server provides Python specific completions and annotations.") + .arg(python.name(), python.path.toUserOutput()); + Core::InfoBarEntry info(installPylsInfoBarId, + message, + Core::InfoBarEntry::GlobalSuppression::Enabled); + info.setCustomButtonInfo(TextEditor::BaseTextEditor::tr("Install"), + [=]() { installPythonLanguageServer(python, document); }); + infoBar->addInfo(info); + } else if (lsState.state == PythonLanguageServerState::AlreadyInstalled && infoBar->canInfoBeAdded(startPylsInfoBarId)) { auto message = PythonEditorFactory::tr("Found a Python language server for %1 (%2). " "Should this one be set up for this document?") @@ -264,3 +363,5 @@ PythonEditorFactory::PythonEditorFactory() } // namespace Internal } // namespace Python + +#include "pythoneditor.moc" diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 99b82116bfb..3d8f5ce3cb2 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -267,6 +267,17 @@ extend_qtc_plugin(QmlDesigner itemlibrarysectionmodel.cpp itemlibrarysectionmodel.h itemlibraryview.cpp itemlibraryview.h itemlibrarywidget.cpp itemlibrarywidget.h + itemlibraryassetimportdialog.cpp itemlibraryassetimportdialog.h + itemlibraryassetimportdialog.ui + itemlibraryassetimporter.cpp itemlibraryassetimporter.h +) + +find_package(Qt5 COMPONENTS Quick3DAssetImport QUIET) +extend_qtc_plugin(QmlDesigner + CONDITION TARGET Qt5::Quick3DAssetImport + FEATURE_INFO "Qt Quick 3D asset import" + DEPENDS Qt5::Quick3DAssetImportPrivate + DEFINES IMPORT_QUICK3D_ASSETS ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon.png b/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon.png new file mode 100644 index 00000000000..1a69afa777c Binary files /dev/null and b/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon.png differ diff --git a/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon@2x.png b/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon@2x.png new file mode 100644 index 00000000000..d3aa02218bf Binary files /dev/null and b/src/plugins/qmldesigner/components/itemlibrary/images/item-3D_model-icon@2x.png differ diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri index b1f2543dc1f..0e6219dd9ac 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri @@ -1,5 +1,10 @@ VPATH += $$PWD +qtHaveModule(quick3dassetimport) { + QT *= quick3dassetimport-private + DEFINES *= IMPORT_QUICK3D_ASSETS +} + # Input HEADERS += itemlibraryview.h \ itemlibrarywidget.h \ @@ -9,6 +14,8 @@ HEADERS += itemlibraryview.h \ itemlibrarysectionmodel.h \ itemlibraryitem.h \ itemlibrarysection.h \ + itemlibraryassetimportdialog.h \ + itemlibraryassetimporter.h \ customfilesystemmodel.h SOURCES += itemlibraryview.cpp \ @@ -19,6 +26,9 @@ SOURCES += itemlibraryview.cpp \ itemlibrarysectionmodel.cpp \ itemlibraryitem.cpp \ itemlibrarysection.cpp \ + itemlibraryassetimportdialog.cpp \ + itemlibraryassetimporter.cpp \ customfilesystemmodel.cpp - RESOURCES += itemlibrary.qrc + +FORMS += itemlibraryassetimportdialog.ui diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc index 8e786cae4c9..12600bf7eb8 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc @@ -3,5 +3,7 @@ images/item-default-icon.png images/item-invalid-icon.png images/item-default-icon@2x.png + images/item-3D_model-icon.png + images/item-3D_model-icon@2x.png diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp new file mode 100644 index 00000000000..0dd80c3c9af --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "itemlibraryassetimportdialog.h" +#include "ui_itemlibraryassetimportdialog.h" + +#include "qmldesignerplugin.h" +#include "qmldesignerconstants.h" +#include "model.h" + +#include "utils/outputformatter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +namespace { + +static void addFormattedMessage(Utils::OutputFormatter *formatter, const QString &str, + const QString &srcPath, Utils::OutputFormat format) { + if (!formatter) + return; + QString msg = str; + if (!srcPath.isEmpty()) + msg += QStringLiteral(": \"%1\"").arg(srcPath); + msg += QLatin1Char('\n'); + formatter->appendMessage(msg, format); + formatter->plainTextEdit()->verticalScrollBar()->setValue( + formatter->plainTextEdit()->verticalScrollBar()->maximum()); +} + +} + +ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &importFiles, + const QString &defaulTargetDirectory, QWidget *parent) : + QDialog(parent), + ui(new Ui::ItemLibraryAssetImportDialog), + m_importer(this) +{ + setModal(true); + ui->setupUi(this); + + m_outputFormatter = new Utils::OutputFormatter; + m_outputFormatter->setPlainTextEdit(ui->plainTextEdit); + + // Skip unsupported assets + bool skipSome = false; + for (const auto &file : importFiles) { + if (m_importer.isQuick3DAsset(file)) + m_quick3DFiles << file; + else + skipSome = true; + } + + if (skipSome) + addWarning("Cannot import 3D and other assets simultaneously. Skipping non-3D assets."); + + // Import button will be used in near future when we add import options. Hide for now. + ui->buttonBox->button(QDialogButtonBox::Ok)->hide(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import")); + connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, + this, &ItemLibraryAssetImportDialog::onImport); + + ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true); + + // Import is always done under known folder. The order of preference for folder is: + // 1) An existing QUICK_3D_ASSETS_FOLDER under DEFAULT_ASSET_IMPORT_FOLDER project import path + // 2) An existing QUICK_3D_ASSETS_FOLDER under any project import path + // 3) New QUICK_3D_ASSETS_FOLDER under DEFAULT_ASSET_IMPORT_FOLDER project import path + // 4) New QUICK_3D_ASSETS_FOLDER under any project import path + // 5) New QUICK_3D_ASSETS_FOLDER under new DEFAULT_ASSET_IMPORT_FOLDER under project + const QString defaultAssetFolder = QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER); + const QString quick3DFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); + QString candidatePath = defaulTargetDirectory + defaultAssetFolder + quick3DFolder; + int candidatePriority = 5; + QStringList importPaths; + + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + if (doc) { + Model *model = doc->currentModel(); + if (model) + importPaths = model->importPaths(); + } + + for (auto importPath : qAsConst(importPaths)) { + if (importPath.startsWith(defaulTargetDirectory)) { + const bool isDefaultFolder = importPath.endsWith(defaultAssetFolder); + const QString assetFolder = importPath + quick3DFolder; + const bool exists = QFileInfo(assetFolder).exists(); + if (exists) { + if (isDefaultFolder) { + // Priority one location, stop looking + candidatePath = assetFolder; + break; + } else if (candidatePriority > 2) { + candidatePriority = 2; + candidatePath = assetFolder; + } + } else { + if (candidatePriority > 3 && isDefaultFolder) { + candidatePriority = 3; + candidatePath = assetFolder; + } else if (candidatePriority > 4) { + candidatePriority = 4; + candidatePath = assetFolder; + } + } + } + } + m_quick3DImportPath = candidatePath; + + // Queue import immediately until we have some options + QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::onImport); + + connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, + this, &ItemLibraryAssetImportDialog::onClose); + + connect(&m_importer, &ItemLibraryAssetImporter::errorReported, + this, &ItemLibraryAssetImportDialog::addError); + connect(&m_importer, &ItemLibraryAssetImporter::warningReported, + this, &ItemLibraryAssetImportDialog::addWarning); + connect(&m_importer, &ItemLibraryAssetImporter::infoReported, + this, &ItemLibraryAssetImportDialog::addInfo); + connect(&m_importer, &ItemLibraryAssetImporter::importFinished, + this, &ItemLibraryAssetImportDialog::onImportFinished); + connect(&m_importer, &ItemLibraryAssetImporter::progressChanged, + this, &ItemLibraryAssetImportDialog::setImportProgress); +} + +ItemLibraryAssetImportDialog::~ItemLibraryAssetImportDialog() +{ + delete ui; +} + +void ItemLibraryAssetImportDialog::setImportUiState(bool importing) +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!importing); + ui->buttonBox->button(QDialogButtonBox::Close)->setText(importing ? tr("Cancel") : tr("Close")); +} + +void ItemLibraryAssetImportDialog::addError(const QString &error, const QString &srcPath) +{ + addFormattedMessage(m_outputFormatter, error, srcPath, Utils::StdErrFormat); +} + +void ItemLibraryAssetImportDialog::addWarning(const QString &warning, const QString &srcPath) +{ + addFormattedMessage(m_outputFormatter, warning, srcPath, Utils::StdOutFormat); +} + +void ItemLibraryAssetImportDialog::addInfo(const QString &info, const QString &srcPath) +{ + addFormattedMessage(m_outputFormatter, info, srcPath, Utils::NormalMessageFormat); +} + +void ItemLibraryAssetImportDialog::onImport() +{ + setImportUiState(true); + ui->progressBar->setValue(0); + ui->plainTextEdit->clear(); + + if (!m_quick3DFiles.isEmpty()) + m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath); +} + +void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &text) +{ + ui->progressLabel->setText(text); + if (value < 0) + ui->progressBar->setRange(0, 0); + else + ui->progressBar->setRange(0, 100); + ui->progressBar->setValue(value); +} + +void ItemLibraryAssetImportDialog::onImportFinished() +{ + setImportUiState(false); + if (m_importer.isCancelled()) { + QString interruptStr = tr("Import interrupted."); + addError(interruptStr); + setImportProgress(0, interruptStr); + } else { + QString doneStr = tr("Import done."); + addInfo(doneStr); + setImportProgress(100, doneStr); + } +} + +void ItemLibraryAssetImportDialog::onClose() +{ + if (m_importer.isImporting()) { + addInfo(tr("Canceling import.")); + m_importer.cancelImport(); + } else { + reject(); + } + close(); + deleteLater(); +} +} diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h new file mode 100644 index 00000000000..93fcc58fc67 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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. +** +****************************************************************************/ +#pragma once + +#include "itemlibraryassetimporter.h" + +#include + +namespace Utils { +class OutputFormatter; +} + +namespace QmlDesigner { +class ItemLibraryAssetImporter; + +namespace Ui { +class ItemLibraryAssetImportDialog; +} + +class ItemLibraryAssetImportDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ItemLibraryAssetImportDialog(const QStringList &importFiles, + const QString &defaulTargetDirectory, QWidget *parent = nullptr); + ~ItemLibraryAssetImportDialog(); + +private slots: + void addError(const QString &error, const QString &srcPath = {}); + void addWarning(const QString &warning, const QString &srcPath = {}); + void addInfo(const QString &info, const QString &srcPath = {}); + +private: + void setImportUiState(bool importing); + + void onImport(); + void setImportProgress(int value, const QString &text); + void onImportFinished(); + void onClose(); + + Ui::ItemLibraryAssetImportDialog *ui = nullptr; + Utils::OutputFormatter *m_outputFormatter = nullptr; + + QStringList m_quick3DFiles; + QString m_quick3DImportPath; + ItemLibraryAssetImporter m_importer; +}; +} diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui new file mode 100644 index 00000000000..7cccc494144 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui @@ -0,0 +1,49 @@ + + + QmlDesigner::ItemLibraryAssetImportDialog + + + + 0 + 0 + 800 + 480 + + + + Asset Import + + + + + + true + + + + + + + + + + + + + + 0 + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp new file mode 100644 index 00000000000..9aa4ea0262e --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "itemlibraryassetimporter.h" +#include "qmldesignerplugin.h" +#include "qmldesignerconstants.h" + +#include "rewriterview.h" +#include "model.h" + +#include +#include +#include +#include +#include + +#ifdef IMPORT_QUICK3D_ASSETS +#include +#endif + +namespace +{ +Q_LOGGING_CATEGORY(importerLog, "qtc.itemlibrary.assetImporter", QtWarningMsg) +} + +namespace QmlDesigner { + +ItemLibraryAssetImporter::ItemLibraryAssetImporter(QObject *parent) : + QObject (parent) +{ +} + +ItemLibraryAssetImporter::~ItemLibraryAssetImporter() { + cancelImport(); +}; + +void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, + const QString &importPath) +{ + if (m_isImporting) + cancelImport(); + reset(); + m_isImporting = true; + +#ifdef IMPORT_QUICK3D_ASSETS + if (!QDir().mkpath(importPath)) { + addError(tr("Cannot create import directory."), importPath); + notifyFinished(); + return; + } + + m_importPath = importPath; + + parseFiles(inputFiles); + + if (!isCancelled()) { + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + Model *model = doc ? doc->currentModel() : nullptr; + if (model && !m_quick3DAddedImports.isEmpty()) { + const QString progressTitle = tr("Updating data model."); + addInfo(progressTitle); + notifyProgress(0, progressTitle); + + // Trigger underlying qmljs snapshot update by making a non-change to the doc + model->rewriterView()->textModifier()->replace(0, 0, {}); + + // There is a inbuilt delay before rewriter change actually updates the data model, + // so we need to wait for a moment to allow the change to take effect. + // Otherwise subsequent subcomponent manager update won't detect new imports properly. + QTimer *timer = new QTimer(parent()); + static int counter; + counter = 0; + timer->callOnTimeout([this, timer, progressTitle, doc]() { + if (!isCancelled()) { + notifyProgress(++counter * 10, progressTitle); + if (counter >= 10) { + doc->updateSubcomponentManager(); + timer->stop(); + notifyFinished(); + } + } else { + timer->stop(); + } + }); + timer->start(100); + } else { + notifyFinished(); + } + } +#else + Q_UNUSED(inputFiles) + Q_UNUSED(importPath) + addError(tr("Importing 3D assets requires building against Qt Quick 3D module.")); + notifyFinished(); +#endif +} + +bool ItemLibraryAssetImporter::isImporting() const +{ + return m_isImporting; +} + +void ItemLibraryAssetImporter::cancelImport() +{ + m_cancelled = true; +} + +void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath) const +{ + qCDebug(importerLog) << "Error: "<< errMsg << srcPath; + emit errorReported(errMsg, srcPath); +} + +void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath) const +{ + qCDebug(importerLog) << "Warning: " << warningMsg << srcPath; + emit warningReported(warningMsg, srcPath); +} + +void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath) const +{ + qCDebug(importerLog) << "Info: " << infoMsg << srcPath; + emit infoReported(infoMsg, srcPath); +} + +bool ItemLibraryAssetImporter::isQuick3DAsset(const QString &fileName) const +{ + static QStringList quick3DExt; + if (quick3DExt.isEmpty()) { + quick3DExt << QString(Constants::FbxExtension) + << QString(Constants::ColladaExtension) + << QString(Constants::ObjExtension) + << QString(Constants::BlenderExtension) + << QString(Constants::GltfExtension); + } + return quick3DExt.contains(QFileInfo(fileName).suffix()); +} + +void ItemLibraryAssetImporter::notifyFinished() +{ + m_isImporting = false; + emit importFinished(); +} + +void ItemLibraryAssetImporter::reset() +{ + m_isImporting = false; + m_cancelled = false; + +#ifdef IMPORT_QUICK3D_ASSETS + m_quick3DAddedImports.clear(); +#endif +} + +void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths) +{ + if (isCancelled()) + return; + const QString progressTitle = tr("Parsing files."); + addInfo(progressTitle); + notifyProgress(0, progressTitle); + uint count = 0; + double quota = 100.0 / filePaths.count(); + std::function progress = [this, quota, &count, &progressTitle](double value) { + notifyProgress(qRound(quota * (count + value)), progressTitle); + }; + for (const QString &file : filePaths) { + if (isCancelled()) + return; + if (isQuick3DAsset(file)) + parseQuick3DAsset(file); + notifyProgress(qRound(++count * quota), progressTitle); + } + notifyProgress(100, progressTitle); +} + +void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file) +{ +#ifdef IMPORT_QUICK3D_ASSETS + addInfo(tr("Parsing 3D Model"), file); + if (!m_quick3DAssetImporter) + m_quick3DAssetImporter.reset(new QSSGAssetImportManager); + + QString errorString; + + QDir rootDir(m_importPath); + QFileInfo sourceInfo(file); + QString assetName = sourceInfo.completeBaseName(); + + if (!assetName.isEmpty()) { + // Fix name so it plays nice with imports + for (QChar ¤tChar : assetName) { + if (!currentChar.isLetter() && !currentChar.isDigit()) + currentChar = QLatin1Char('_'); + } + QCharRef firstChar = assetName[0]; + if (firstChar.isDigit()) + firstChar = QLatin1Char('_'); + if (firstChar.isLower()) + firstChar = firstChar.toUpper(); + } + + QDir outDir = rootDir; + if (outDir.exists(assetName)) { + if (confirmAssetOverwrite(assetName)) { + if (outDir.cd(assetName)) { + outDir.removeRecursively(); + outDir.cdUp(); + outDir.mkpath(assetName); + } // If cd fails here, it will fail below, too, so no error handling here + } else { + addWarning(tr("Skipped import of existing asset: \"%1\"").arg(assetName)); + return; + } + } else { + outDir.mkpath(assetName); + } + if (!outDir.cd(assetName)) { + addError(tr("Could not access asset directory: \"%1\"").arg(outDir.filePath(assetName))); + return; + } + + addInfo(tr("Generating 3D assets into: \"%1\"").arg(outDir.absolutePath())); + + if (m_quick3DAssetImporter->importFile( + sourceInfo.absoluteFilePath(), outDir, + &errorString) != QSSGAssetImportManager::ImportState::Success) { + addError(tr("Failed to import 3D asset with error: %1").arg(errorString), + sourceInfo.absoluteFilePath()); + return; + } + + // Generate qmldir file + outDir.setNameFilters({QStringLiteral("*.qml")}); + const QFileInfoList qmlFiles = outDir.entryInfoList(QDir::Files); + + if (!qmlFiles.isEmpty()) { + QString qmldirFileName = outDir.absoluteFilePath(QStringLiteral("qmldir")); + QSaveFile qmldirFile(qmldirFileName); + QString version = QStringLiteral("1.0"); + if (qmldirFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + for (const auto &fi : qmlFiles) { + QString qmlInfo; + qmlInfo.append(fi.baseName()); + qmlInfo.append(QLatin1Char(' ')); + qmlInfo.append(version); + qmlInfo.append(QLatin1Char(' ')); + qmlInfo.append(fi.fileName()); + qmldirFile.write(qmlInfo.toUtf8()); + } + QString assetFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); + assetFolder = assetFolder.mid(assetFolder.lastIndexOf(QLatin1Char('/')) + 1); + m_quick3DAddedImports.insert( + qmldirFileName, + Import::createLibraryImport( + QStringLiteral("%1.%2").arg(assetFolder).arg(assetName), version)); + qmldirFile.commit(); + } else { + addError(tr("Failed to create qmldir file for asset: \"%1\"").arg(assetName)); + } + } + + // Copy the original asset into a subdirectory + QString origAssetDirName = QStringLiteral("source model"); + QDir origAssetDir = outDir; + + origAssetDir.mkpath(origAssetDirName); + origAssetDir.cd(origAssetDirName); + QFile::copy(sourceInfo.absoluteFilePath(), origAssetDir.filePath(sourceInfo.fileName())); +#else + Q_UNUSED(file) +#endif +} + +void ItemLibraryAssetImporter::notifyProgress(int value, const QString &text) const +{ + emit progressChanged(value, text); + keepUiAlive(); +} + +void ItemLibraryAssetImporter::keepUiAlive() const +{ + QApplication::processEvents(); +} + +bool ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) +{ + const QString title = tr("Overwrite Existing Asset?"); + const QString question = tr("Asset already exists. Overwrite?\n\"%1\"").arg(assetName); + return QMessageBox::question(qobject_cast(parent()), + title, question, + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; +} + +bool ItemLibraryAssetImporter::isCancelled() const +{ + keepUiAlive(); + return m_cancelled; +} + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h new file mode 100644 index 00000000000..a8e0a5b1a92 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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. +** +****************************************************************************/ +#pragma once + +#include +#include +#include + +#include "import.h" + +QT_BEGIN_NAMESPACE +class QSSGAssetImportManager; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ItemLibraryAssetImporter : public QObject +{ + Q_OBJECT + +public: + ItemLibraryAssetImporter(QObject *parent = nullptr); + ~ItemLibraryAssetImporter(); + + void importQuick3D(const QStringList &inputFiles, const QString &importPath); + + bool isImporting() const; + void cancelImport(); + bool isCancelled() const; + + void addError(const QString &errMsg, const QString &srcPath = {}) const; + void addWarning(const QString &warningMsg, const QString &srcPath = {}) const; + void addInfo(const QString &infoMsg, const QString &srcPath = {}) const; + + bool isQuick3DAsset(const QString &fileName) const; + +signals: + void errorReported(const QString &, const QString &) const; + void warningReported(const QString &, const QString &) const; + void infoReported(const QString &, const QString &) const; + void progressChanged(int value, const QString &text) const; + void importFinished(); + +private: + void notifyFinished(); + void reset(); + void parseFiles(const QStringList &filePaths); + void parseQuick3DAsset(const QString &file); + + void notifyProgress(int value, const QString &text) const; + void keepUiAlive() const; + bool confirmAssetOverwrite(const QString &assetName); + +#ifdef IMPORT_QUICK3D_ASSETS + QScopedPointer m_quick3DAssetImporter; + QHash m_quick3DAddedImports; +#endif + bool m_isImporting = false; + bool m_cancelled = false; + QString m_importPath; +}; +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index b37bbdfa365..f69c00b94d0 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -26,6 +26,7 @@ #include "itemlibrarywidget.h" #include "customfilesystemmodel.h" +#include "itemlibraryassetimportdialog.h" #include @@ -36,6 +37,8 @@ #include #include #include +#include +#include #include #include @@ -183,6 +186,43 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : flowLayout->addWidget(button); connect(button, &QToolButton::clicked, this, &ItemLibraryWidget::addResources); +#ifdef IMPORT_QUICK3D_ASSETS + DesignerActionManager *actionManager = + &QmlDesignerPlugin::instance()->viewManager().designerActionManager(); + + auto handle3DModel = [](const QStringList &fileNames, const QString &defaultDir) -> bool { + auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir); + importDlg->show(); + return true; + }; + + const QString category = tr("3D Models"); + + auto add3DHandler = [&actionManager, &handle3DModel, &category](const char *ext) { + const QString filter = QStringLiteral("*.%1").arg(QString::fromLatin1(ext)); + actionManager->registerAddResourceHandler( + AddResourceHandler(category, filter, handle3DModel, 10)); + }; + + // Skip if 3D model handlers have already been added + const QList handlers = actionManager->addResourceHandler(); + bool categoryAlreadyAdded = false; + for (const auto &handler : handlers) { + if (handler.category == category) { + categoryAlreadyAdded = true; + break; + } + } + + if (!categoryAlreadyAdded) { + add3DHandler(Constants::FbxExtension); + add3DHandler(Constants::ColladaExtension); + add3DHandler(Constants::ObjExtension); + add3DHandler(Constants::BlenderExtension); + add3DHandler(Constants::GltfExtension); + } +#endif + // init the first load of the QML UI elements reloadQmlSource(); } @@ -192,13 +232,19 @@ void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) if (m_itemLibraryInfo.data() == itemLibraryInfo) return; - if (m_itemLibraryInfo) + if (m_itemLibraryInfo) { disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, this, &ItemLibraryWidget::delayedUpdateModel); + disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::importTagsChanged, + this, &ItemLibraryWidget::delayedUpdateModel); + } m_itemLibraryInfo = itemLibraryInfo; - if (itemLibraryInfo) + if (itemLibraryInfo) { connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, this, &ItemLibraryWidget::delayedUpdateModel); + connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::importTagsChanged, + this, &ItemLibraryWidget::delayedUpdateModel); + } delayedUpdateModel(); } @@ -383,7 +429,22 @@ void ItemLibraryWidget::addPossibleImport(const QString &name) QTC_ASSERT(m_model, return); const Import import = m_model->highestPossibleImport(name); try { - m_model->changeImports({Import::createLibraryImport(name, import.version())}, {}); + QList addedImports = {Import::createLibraryImport(name, import.version())}; + // Special case for adding an import for 3D asset - also add QtQuick3D import + const QString asset3DPrefix = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER + 1) + + QLatin1Char('.'); + if (name.startsWith(asset3DPrefix)) { + const QString q3Dlib = QLatin1String(Constants::QT_QUICK_3D_MODULE_NAME); + Import q3DImport = m_model->highestPossibleImport(q3Dlib); + if (q3DImport.url() == q3Dlib) + addedImports.prepend(Import::createLibraryImport(q3Dlib, q3DImport.version())); + } + RewriterTransaction transaction + = m_model->rewriterView()->beginRewriterTransaction( + QByteArrayLiteral("ItemLibraryWidget::addPossibleImport")); + + m_model->changeImports(addedImports, {}); + transaction.commit(); } catch (const RewritingException &e) { e.showException(); diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index aed724e50fe..6c91d4917c2 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -114,6 +114,7 @@ public: signals: void entriesChanged(); + void importTagsChanged(); private: // functions ItemLibraryInfo(QObject *parent = nullptr); diff --git a/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h b/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h index da21c17cd92..674f7dff494 100644 --- a/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h +++ b/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h @@ -65,6 +65,8 @@ private: // functions void registerQmlFile(const QFileInfo &fileInfo, const QString &qualifier, bool addToLibrary); Model *model() const; QStringList importPaths() const; + void parseQuick3DAssetDir(const QString &assetPath); + QStringList quick3DAssetPaths() const; private: // variables QFileSystemWatcher m_watcher; diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index 4251dd96c30..32c023464a9 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -356,7 +356,10 @@ void ItemLibraryInfo::addBlacklistImports(const QStringList &list) void ItemLibraryInfo::addShowTagsForImports(const QStringList &list) { - m_showTagsForImports.append(list); + if (!list.isEmpty()) { + m_showTagsForImports.append(list); + emit importTagsChanged(); + } } void ItemLibraryInfo::setBaseInfo(ItemLibraryInfo *baseInfo) diff --git a/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp b/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp index 85a84af9a3f..0c707ff837b 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp @@ -85,8 +85,7 @@ void SubComponentManager::addImport(int pos, const Import &import) url.replace(QLatin1Char('.'), QLatin1Char('/')); foreach (const QString &path, importPaths()) { - url = path + QLatin1Char('/') + url; - QFileInfo dirInfo = QFileInfo(url); + QFileInfo dirInfo = QFileInfo(path + QLatin1Char('/') + url); if (dirInfo.exists() && dirInfo.isDir()) { const QString canonicalDirPath = dirInfo.canonicalFilePath(); m_watcher.addPath(canonicalDirPath); @@ -126,14 +125,19 @@ void SubComponentManager::parseDirectories() if (!m_filePath.isEmpty()) { const QString file = m_filePath.toLocalFile(); QFileInfo dirInfo = QFileInfo(QFileInfo(file).path()); + const QString canonicalPath = dirInfo.canonicalFilePath(); if (dirInfo.exists() && dirInfo.isDir()) - parseDirectory(dirInfo.canonicalFilePath()); + parseDirectory(canonicalPath); foreach (const QString &subDir, QDir(QFileInfo(file).path()).entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot)) { - parseDirectory(dirInfo.canonicalFilePath() + QLatin1String("/") + subDir, true, subDir.toUtf8()); + parseDirectory(canonicalPath + QLatin1Char('/') + subDir, true, subDir.toUtf8()); } } + const QStringList assetPaths = quick3DAssetPaths(); + for (const auto &assetPath : assetPaths) + parseDirectory(assetPath); + foreach (const Import &import, m_imports) { if (import.isFileImport()) { QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.file()).toLocalFile()); @@ -169,6 +173,11 @@ void SubComponentManager::parseDirectory(const QString &canonicalDirPath, bool a if (!model() || !model()->rewriterView()) return; + if (canonicalDirPath.endsWith(QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER))) { + parseQuick3DAssetDir(canonicalDirPath); + return; + } + QDir designerDir(canonicalDirPath + QLatin1String(Constants::QML_DESIGNER_SUBFOLDER)); if (designerDir.exists()) { QStringList filter; @@ -325,7 +334,7 @@ void SubComponentManager::registerQmlFile(const QFileInfo &fileInfo, const QStri ItemLibraryEntry itemLibraryEntry; itemLibraryEntry.setType(componentName.toUtf8()); itemLibraryEntry.setName(baseComponentName); - itemLibraryEntry.setCategory(QLatin1String("My QML Components")); + itemLibraryEntry.setCategory(tr("My QML Components")); if (!qualifier.isEmpty()) { itemLibraryEntry.setRequiredImport(fixedQualifier); } @@ -348,6 +357,75 @@ QStringList SubComponentManager::importPaths() const return QStringList(); } +void SubComponentManager::parseQuick3DAssetDir(const QString &assetPath) +{ + QDir assetDir(assetPath); + const QString assetImportRoot = assetPath.mid(assetPath.lastIndexOf(QLatin1Char('/')) + 1); + QStringList assets = assetDir.entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot); + for (QString &asset : assets) + asset.prepend(assetImportRoot + QLatin1Char('.')); + + QStringList newFlowTags; + const QStringList flowTags = model()->metaInfo().itemLibraryInfo()->showTagsForImports(); + const QString quick3Dlib = QLatin1String(Constants::QT_QUICK_3D_MODULE_NAME); + const QList possibleImports = model()->possibleImports(); + + auto isPossibleImport = [&possibleImports](const QString &asset) { + for (const Import &import : possibleImports) { + if (import.url() == asset) + return true; + } + return false; + }; + + // If there are 3D assets in import path, add a flow tag for QtQuick3D + if (!assets.isEmpty() && !flowTags.contains(quick3Dlib) && isPossibleImport(quick3Dlib)) + newFlowTags << quick3Dlib; + + // Create item library entries for Quick3D assets that are imported by document + const QString iconPath = QStringLiteral(":/ItemLibrary/images/item-3D_model-icon.png"); + for (auto &import : qAsConst(m_imports)) { + if (import.isLibraryImport() && assets.contains(import.url())) { + assets.removeOne(import.url()); + ItemLibraryEntry itemLibraryEntry; + const QString name = import.url().mid(import.url().indexOf(QLatin1Char('.')) + 1); + const QString type = import.url() + QLatin1Char('.') + name; + // For now we assume version is always 1.0 as that's what importer hardcodes + itemLibraryEntry.setType(type.toUtf8(), 1, 0); + itemLibraryEntry.setName(name); + itemLibraryEntry.setCategory(tr("My Quick3D Components")); + itemLibraryEntry.setRequiredImport(import.url()); + itemLibraryEntry.setLibraryEntryIconPath(iconPath); + itemLibraryEntry.setTypeIcon(QIcon(iconPath)); + + if (!model()->metaInfo().itemLibraryInfo()->containsEntry(itemLibraryEntry)) + model()->metaInfo().itemLibraryInfo()->addEntries({itemLibraryEntry}); + } + } + + // Create flow tags for the rest, if they are possible imports + if (!assets.isEmpty()) { + for (const QString &asset : qAsConst(assets)) { + if (!flowTags.contains(asset) && isPossibleImport(asset)) + newFlowTags << asset; + } + } + + if (!newFlowTags.isEmpty()) + model()->metaInfo().itemLibraryInfo()->addShowTagsForImports(newFlowTags); +} + +QStringList SubComponentManager::quick3DAssetPaths() const +{ + const auto impPaths = importPaths(); + QStringList retPaths; + for (const auto &impPath : impPaths) { + const QString assetPath = impPath + QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); + if (QFileInfo(assetPath).exists()) + retPaths << assetPath; + } + return retPaths; +} /*! \class SubComponentManager @@ -392,6 +470,14 @@ void SubComponentManager::update(const QUrl &filePath, const QList &impo m_dirToQualifier.remove(oldDir.canonicalFilePath(), QString()); if (!m_dirToQualifier.contains(oldDir.canonicalFilePath())) m_watcher.removePath(oldDir.filePath()); + + // Remove old watched asset paths + const QStringList watchPaths = m_watcher.directories(); + const QString &quick3DAssetFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); + for (const auto &watchPath : watchPaths) { + if (watchPath.endsWith(quick3DAssetFolder)) + m_watcher.removePath(watchPath); + } } if (!newDir.filePath().isEmpty()) @@ -417,7 +503,13 @@ void SubComponentManager::update(const QUrl &filePath, const QList &impo addImport(ii, imports.at(ii)); } - m_watcher.addPath(newDir.absoluteFilePath()); + const QString newPath = newDir.absoluteFilePath(); + m_watcher.addPath(newPath); + + // Watch existing asset paths, including a global ones if they exist + const auto assetPaths = quick3DAssetPaths(); + for (const auto &assetPath : assetPaths) + m_watcher.addPath(assetPath); parseDirectories(); } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 85350917f03..0f75d535819 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -50,6 +50,15 @@ const char GO_INTO_COMPONENT[] = "QmlDesigner.GoIntoComponent"; const char EXPORT_AS_IMAGE[] = "QmlDesigner.ExportAsImage"; const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; +const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets"; +const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports"; +const char QT_QUICK_3D_MODULE_NAME[] = "QtQuick3D"; + +const char FbxExtension[] = "fbx"; +const char ColladaExtension[] = "dae"; +const char ObjExtension[] = "obj"; +const char BlenderExtension[] = "blend"; +const char GltfExtension[] = "gltf"; namespace Internal { enum { debug = 0 }; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 1e82b1354db..662b38ac3a4 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -524,6 +524,11 @@ Project { "integration/utilitypanelcontroller.cpp", "integration/utilitypanelcontroller.h", "itemlibrary/itemlibrary.qrc", + "itemlibrary/itemlibraryassetimportdialog.cpp", + "itemlibrary/itemlibraryassetimportdialog.h", + "itemlibrary/itemlibraryassetimportdialog.ui", + "itemlibrary/itemlibraryassetimporter.cpp", + "itemlibrary/itemlibraryassetimporter.h", "itemlibrary/itemlibraryimageprovider.cpp", "itemlibrary/itemlibraryimageprovider.h", "itemlibrary/itemlibraryitem.cpp", diff --git a/src/plugins/qnx/qnxplugin.cpp b/src/plugins/qnx/qnxplugin.cpp index a0780561369..c9f124b31a8 100644 --- a/src/plugins/qnx/qnxplugin.cpp +++ b/src/plugins/qnx/qnxplugin.cpp @@ -55,6 +55,7 @@ #include #include +#include #include #include @@ -90,6 +91,11 @@ public: addSupportedTargetDeviceType(Constants::QNX_QNX_OS_TYPE); setUseDeploymentDataView(); + addInitialStep(RemoteLinux::MakeInstallStep::stepId(), [](Target *target) { + const Project * const prj = target->project(); + return prj->deploymentKnowledge() == DeploymentKnowledge::Bad + && prj->hasMakeInstallEquivalent(); + }); addInitialStep(DeviceCheckBuildStep::stepId()); addInitialStep(RemoteLinux::RemoteLinuxCheckForFreeDiskSpaceStep::stepId()); addInitialStep(RemoteLinux::GenericDirectUploadStep::stepId()); @@ -110,6 +116,7 @@ public: QnxDeployConfigurationFactory deployConfigFactory; GenericQnxDeployStepFactory directUploadDeployFactory; GenericQnxDeployStepFactory checkForFreeDiskSpaceDeployFactory; + GenericQnxDeployStepFactory makeInstallDeployFactory; GenericQnxDeployStepFactory checkBuildDeployFactory; QnxRunConfigurationFactory runConfigFactory; QnxSettingsPage settingsPage; diff --git a/src/plugins/remotelinux/makeinstallstep.cpp b/src/plugins/remotelinux/makeinstallstep.cpp index 3c2b798ca50..ccc19418fe3 100644 --- a/src/plugins/remotelinux/makeinstallstep.cpp +++ b/src/plugins/remotelinux/makeinstallstep.cpp @@ -45,7 +45,6 @@ using namespace ProjectExplorer; using namespace Utils; namespace RemoteLinux { -namespace Internal { const char MakeAspectId[] = "RemoteLinux.MakeInstall.Make"; const char InstallRootAspectId[] = "RemoteLinux.MakeInstall.InstallRoot"; @@ -228,5 +227,4 @@ bool MakeInstallStep::fromMap(const QVariantMap &map) return true; } -} // namespace Internal } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/makeinstallstep.h b/src/plugins/remotelinux/makeinstallstep.h index 78f9029a9e6..9cc27c3435d 100644 --- a/src/plugins/remotelinux/makeinstallstep.h +++ b/src/plugins/remotelinux/makeinstallstep.h @@ -25,15 +25,16 @@ #pragma once +#include "remotelinux_export.h" + #include #include namespace Utils { class FilePath; } namespace RemoteLinux { -namespace Internal { -class MakeInstallStep : public ProjectExplorer::MakeStep +class REMOTELINUX_EXPORT MakeInstallStep : public ProjectExplorer::MakeStep { Q_OBJECT public: @@ -62,5 +63,4 @@ private: bool m_isCmakeProject = false; }; -} // namespace Internal } // namespace RemoteLinux diff --git a/src/shared/qbs b/src/shared/qbs index 94fe404a5a6..aec975a3f95 160000 --- a/src/shared/qbs +++ b/src/shared/qbs @@ -1 +1 @@ -Subproject commit 94fe404a5a6d7cf91926bcfbd026953994b25815 +Subproject commit aec975a3f95f905b2d63ea1500ace28eddea7b9e