From 6cf8d7d64572b01c180740c81708d3b02b528a3b Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 24 Mar 2020 13:51:16 +0200 Subject: [PATCH] QmlDesigner: Create thumbnail for imported 3D assets Qmlpuppet now allows creating icon from qml source code. Invoke qmlpuppet with following arguments to create icons: --rendericon E.g. --rendericon 24 ~/my_icon.png ~/my_icon.qml Two icons are created, one with size x size dimensions and one with double the dimensions and "@2x" injected into the file name. 3D asset import utilizes this icon rendering to produce item library icons for imported components. Change-Id: I92c62c80d961f5f61a0ce1c09b32bbcbf80ea56c Fixes: QDS-1052 Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../qmlpuppet/mockfiles/IconRenderer3D.qml | 92 +++++++++ .../qml2puppet/editor3d/generalhelper.cpp | 2 +- .../qml2puppet/iconrenderer/iconrenderer.cpp | 181 ++++++++++++++++++ .../qml2puppet/iconrenderer/iconrenderer.h | 56 ++++++ .../qml2puppet/iconrenderer/iconrenderer.pri | 3 + .../qml/qmlpuppet/qml2puppet/qml2puppet.pri | 1 + .../qmlpuppet/qml2puppet/qml2puppetmain.cpp | 14 +- share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc | 1 + .../itemlibrary/itemlibraryassetimporter.cpp | 175 ++++++++++++----- .../itemlibrary/itemlibraryassetimporter.h | 8 + .../designercore/instances/puppetcreator.cpp | 18 +- .../designercore/instances/puppetcreator.h | 6 +- src/tools/qml2puppet/CMakeLists.txt | 6 + src/tools/qml2puppet/qml2puppet.qbs | 2 + 14 files changed, 506 insertions(+), 59 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.pri diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml new file mode 100644 index 00000000000..b16b27406d9 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick3D 1.15 + +Item { + id: viewRoot + width: 1024 + height: 1024 + visible: true + + property alias view3D: view3D + property alias camPos: viewCamera.position + + function setSceneToBox() + { + selectionBox.targetNode = view3D.importScene; + } + + function fitAndHideBox() : bool + { + cameraControl.focusObject(selectionBox.model, viewCamera.eulerRotation, true); + if (cameraControl._zoomFactor < 0.1) { + view3D.importScene.scale = view3D.importScene.scale.times(10); + return false; + } + if (cameraControl._zoomFactor > 100) { + view3D.importScene.scale = view3D.importScene.scale.times(0.1); + return false; + } + + selectionBox.visible = false; + return true + } + + View3D { + id: view3D + camera: viewCamera + environment: sceneEnv + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.VeryHigh + } + + PerspectiveCamera { + id: viewCamera + position: Qt.vector3d(-200, 200, 200) + eulerRotation: Qt.vector3d(-45, -45, 0) + } + + DirectionalLight { + rotation: viewCamera.rotation + } + + SelectionBox { + id: selectionBox + view3D: view3D + geometryName: "SB" + } + + EditCameraController { + id: cameraControl + camera: view3D.camera + view3d: view3D + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 8fe5395a6a5..f99d219c5c5 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -200,7 +200,7 @@ QVector4D GeneralHelper::focusObjectToCamera(QQuick3DCamera *camera, float defau camera->setPosition(lookAt + newLookVector); - float newZoomFactor = updateZoom ? qBound(.01f, float(maxExtent / 700.), 100.f) : oldZoom; + float newZoomFactor = updateZoom ? qBound(.01f, float(maxExtent / 900.), 100.f) : oldZoom; float cameraZoomFactor = zoomCamera(camera, 0, defaultLookAtDistance, lookAt, newZoomFactor, false); return QVector4D(lookAt, cameraZoomFactor); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp new file mode 100644 index 00000000000..034a809b16f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "iconrenderer.h" +#include "../editor3d/selectionboxgeometry.h" +#include "../editor3d/generalhelper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef QUICK3D_MODULE +#include +#include +#endif + +#include + +IconRenderer::IconRenderer(int size, const QString &filePath, const QString &source) + : QObject(nullptr) + , m_size(size) + , m_filePath(filePath) + , m_source(source) +{ +} + +void IconRenderer::setupRender() +{ + DesignerSupport::activateDesignerMode(); + DesignerSupport::activateDesignerWindowManager(); + + m_quickView = new QQuickView; + + QSurfaceFormat surfaceFormat = m_quickView->requestedFormat(); + surfaceFormat.setVersion(4, 1); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + m_quickView->setFormat(surfaceFormat); + + DesignerSupport::createOpenGLContext(m_quickView); + + QQmlComponent component(m_quickView->engine()); + component.loadUrl(QUrl::fromLocalFile(m_source)); + QObject *iconItem = component.create(); + + if (iconItem) { + QQuickItem *containerItem = nullptr; + bool is3D = false; +#ifdef QUICK3D_MODULE + if (auto scene = qobject_cast(iconItem)) { + qmlRegisterType("SelectionBoxGeometry", 1, 0, "SelectionBoxGeometry"); + QQmlComponent component(m_quickView->engine()); + component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/IconRenderer3D.qml")); + containerItem = qobject_cast(component.create()); + DesignerSupport::setRootItem(m_quickView, containerItem); + + auto helper = new QmlDesigner::Internal::GeneralHelper(); + m_quickView->engine()->rootContext()->setContextProperty("_generalHelper", helper); + + m_contentItem = QQmlProperty::read(containerItem, "view3D").value(); + auto view3D = qobject_cast(m_contentItem); + view3D->setImportScene(scene); + is3D = true; + } else +#endif + if (auto scene = qobject_cast(iconItem)) { + m_contentItem = scene; + containerItem = new QQuickItem(); + containerItem->setSize(QSizeF(1024, 1024)); + DesignerSupport::setRootItem(m_quickView, containerItem); + m_contentItem->setParentItem(containerItem); + } + + if (containerItem && m_contentItem) { + m_contentItem->setSize(QSizeF(m_size, m_size)); + if (m_contentItem->width() > containerItem->width()) + containerItem->setWidth(m_contentItem->width()); + if (m_contentItem->height() > containerItem->height()) + containerItem->setHeight(m_contentItem->height()); + + QTimer::singleShot(0, this, [this, containerItem, is3D]() { + m_designerSupport.refFromEffectItem(m_quickView->rootObject(), false); + QQuickDesignerSupportItems::disableNativeTextRendering(m_quickView->rootObject()); + +#ifdef QUICK3D_MODULE + if (is3D) { + // Render once to make sure scene is up to date before we set up the selection box + render({}); + QMetaObject::invokeMethod(containerItem, "setSceneToBox"); + bool success = false; + int tries = 0; + while (!success && tries < 10) { + ++tries; + render({}); + QMetaObject::invokeMethod(containerItem, "fitAndHideBox", + Q_RETURN_ARG(bool, success)); + } + } +#else + Q_UNUSED(is3D) +#endif + QFileInfo fi(m_filePath); + + // Render regular size image + render(fi.absoluteFilePath()); + + // Render @2x image + m_contentItem->setSize(QSizeF(m_size * 2, m_size * 2)); + + QString saveFile; + saveFile = fi.absolutePath() + '/' + fi.completeBaseName() + "@2x"; + if (!fi.suffix().isEmpty()) + saveFile += '.' + fi.suffix(); + + fi.absoluteDir().mkpath("."); + + render(saveFile); + + // Allow little time for file operations to finish + QTimer::singleShot(1000, qGuiApp, &QGuiApplication::quit); + }); + } else { + qGuiApp->quit(); + } + } else { + qGuiApp->quit(); + } +} + +void IconRenderer::render(const QString &fileName) +{ + std::function updateNodesRecursive; + updateNodesRecursive = [&updateNodesRecursive](QQuickItem *item) { + const auto childItems = item->childItems(); + for (QQuickItem *childItem : childItems) + updateNodesRecursive(childItem); + DesignerSupport::updateDirtyNode(item); + }; + updateNodesRecursive(m_quickView->rootObject()); + + QRect rect(QPoint(), m_contentItem->size().toSize()); + QImage renderImage = m_designerSupport.renderImageForItem(m_quickView->rootObject(), + rect, rect.size()); + if (!fileName.isEmpty()) { + QFileInfo fi(fileName); + if (fi.suffix().isEmpty()) + renderImage.save(fileName, "PNG"); + else + renderImage.save(fileName); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h new file mode 100644 index 00000000000..f7bab0f6fc6 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 + +QT_BEGIN_NAMESPACE +class QQuickView; +class QQuickItem; +QT_END_NAMESPACE + +class IconRenderer : public QObject +{ + Q_OBJECT + +public: + explicit IconRenderer(int size, const QString &filePath, const QString &source); + + void setupRender(); + +private: + void render(const QString &fileName); + + int m_size = 16; + QString m_filePath; + QString m_source; + QQuickView *m_quickView = nullptr; + QQuickItem *m_contentItem = nullptr; + DesignerSupport m_designerSupport; +}; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.pri new file mode 100644 index 00000000000..59d5b12cee1 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.pri @@ -0,0 +1,3 @@ +HEADERS += $$PWD/iconrenderer.h + +SOURCES += $$PWD/iconrenderer.cpp diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri index d0bab57ca1a..b8013102767 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri @@ -13,6 +13,7 @@ include (../container/container.pri) include (../interfaces/interfaces.pri) include (../types/types.pri) include (../qmlprivategate/qmlprivategate.pri) +include (iconrenderer/iconrenderer.pri) SOURCES += $$PWD/qml2puppetmain.cpp RESOURCES += $$PWD/../qmlpuppet.qrc diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp index 70f403512fa..ee2f5bac599 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp @@ -32,6 +32,7 @@ #include #include +#include "iconrenderer/iconrenderer.h" #include #include @@ -52,11 +53,13 @@ int internalMain(QGuiApplication *application) QCoreApplication::setApplicationVersion("1.0.0"); if (application->arguments().count() < 2 - || (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() < 3)) { + || (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() < 3) + || (application->arguments().at(1) == "--rendericon" && application->arguments().count() < 5)) { qDebug() << "Usage:\n"; qDebug() << "--test"; qDebug() << "--version"; qDebug() << "--readcapturedstream [control stream file]"; + qDebug() << "--rendericon "; return -1; } @@ -108,7 +111,16 @@ int internalMain(QGuiApplication *application) return -1; } + if (application->arguments().at(1) == "--rendericon") { + int size = application->arguments().at(2).toInt(); + QString iconFileName = application->arguments().at(3); + QString iconSource = application->arguments().at(4); + IconRenderer *iconRenderer = new IconRenderer(size, iconFileName, iconSource); + iconRenderer->setupRender(); + + return application->exec(); + } #ifdef ENABLE_QT_BREAKPAD const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH; diff --git a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc index 0585896fedb..81f28c13ed3 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc +++ b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc @@ -7,6 +7,7 @@ mockfiles/SwipeView.qml mockfiles/GenericBackend.qml mockfiles/Dialog.qml + mockfiles/IconRenderer3D.qml mockfiles/EditView3D.qml mockfiles/EditCameraController.qml mockfiles/Arrow.qml diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index b50322b4bb2..5d2686e8995 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -28,6 +28,7 @@ #include "rewriterview.h" #include "model.h" +#include "puppetcreator.h" #include #include @@ -84,44 +85,14 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, parseFiles(inputFiles, options, extToImportOptionsMap); if (!isCancelled()) { - // Don't allow cancel anymore as existing asset overwrites are not trivially recoverable. - // Also, on Windows at least you can't delete a subdirectory of a watched directory, - // so complete rollback is no longer possible in any case. - emit importNearlyFinished(); - - copyImportedFiles(); - - auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); - Model *model = doc ? doc->currentModel() : nullptr; - if (model && !m_importFiles.isEmpty()) { - const QString progressTitle = tr("Updating data model."); + // Wait for icon generation processes to finish + if (m_qmlPuppetProcesses.isEmpty()) { + finalizeQuick3DImport(); + } else { + m_qmlPuppetCount = m_qmlPuppetProcesses.size(); + const QString progressTitle = tr("Generating icons."); 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 @@ -208,6 +179,26 @@ QHash ItemLibraryAssetImporter::supportedExtensions() cons #endif } +void ItemLibraryAssetImporter::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED(exitCode) + Q_UNUSED(exitStatus) + + auto process = qobject_cast(sender()); + if (process) { + m_qmlPuppetProcesses.remove(process); + process->deleteLater(); + const QString progressTitle = tr("Generating icons."); + if (m_qmlPuppetProcesses.isEmpty()) { + notifyProgress(100, progressTitle); + finalizeQuick3DImport(); + } else { + notifyProgress(int(100. * (1. - double(m_qmlPuppetCount) / double(m_qmlPuppetProcesses.size()))), + progressTitle); + } + } +} + void ItemLibraryAssetImporter::notifyFinished() { m_isImporting = false; @@ -224,6 +215,9 @@ void ItemLibraryAssetImporter::reset() m_tempDir = new QTemporaryDir; m_importFiles.clear(); m_overwrittenImports.clear(); + qDeleteAll(m_qmlPuppetProcesses); + m_qmlPuppetProcesses.clear(); + m_qmlPuppetCount = 0; #endif } @@ -307,6 +301,14 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar return; } + QHash assetFiles; + const int outDirPathSize = outDir.path().size(); + auto insertAsset = [&](const QString &filePath) { + QString targetPath = filePath.mid(outDirPathSize); + targetPath.prepend(targetDirPath); + assetFiles.insert(filePath, targetPath); + }; + // Generate qmldir file if importer doesn't already make one QString qmldirFileName = outDir.absoluteFilePath(QStringLiteral("qmldir")); if (!QFileInfo(qmldirFileName).exists()) { @@ -349,8 +351,6 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar int nlIdx = content.lastIndexOf('\n', braceIdx); QByteArray rootItem = content.mid(nlIdx, braceIdx - nlIdx).trimmed(); if (rootItem == "Node") { // a 3D object - QFile::copy(":/ItemLibrary/images/item-3D_model-icon.png", iconFileName); - QFile::copy(":/ItemLibrary/images/item-3D_model-icon@2x.png", iconFileName2x); // create hints file with proper hints QFile file(outDir.path() + '/' + fi.baseName() + ".hints"); file.open(QIODevice::WriteOnly | QIODevice::Text); @@ -359,9 +359,14 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar out << "canBeDroppedInFormEditor: false" << endl; out << "canBeDroppedInView3D: true" << endl; file.close(); - } else { - QFile::copy(":/ItemLibrary/images/item-default-icon.png", iconFileName); - QFile::copy(":/ItemLibrary/images/item-default-icon@2x.png", iconFileName2x); + } + QString outIconSource = QString::fromUtf8(content); + if (generateComponentIcon(24, iconFileName, qmlIt.filePath())) { + // Since icon is generated by external process, the file won't be + // ready for asset gathering below, so assume its generation succeeds + // and add it now. + insertAsset(iconFileName); + insertAsset(iconFileName2x); } } } @@ -375,15 +380,10 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar } // Gather all generated files - const int outDirPathSize = outDir.path().size(); QDirIterator dirIt(outDir.path(), QDir::Files, QDirIterator::Subdirectories); - QHash assetFiles; while (dirIt.hasNext()) { dirIt.next(); - const QString filePath = dirIt.filePath(); - QString targetPath = filePath.mid(outDirPathSize); - targetPath.prepend(targetDirPath); - assetFiles.insert(filePath, targetPath); + insertAsset(dirIt.filePath()); } // Copy the original asset into a subdirectory @@ -428,10 +428,12 @@ void ItemLibraryAssetImporter::copyImportedFiles() // by filesystem watchers. QHash::const_iterator it = assetFiles.begin(); while (it != assetFiles.end()) { - QDir targetDir = QFileInfo(it.value()).dir(); - if (!targetDir.exists()) - targetDir.mkpath(QStringLiteral(".")); - QFile::copy(it.key(), it.value()); + if (QFileInfo(it.key()).exists()) { + QDir targetDir = QFileInfo(it.value()).dir(); + if (!targetDir.exists()) + targetDir.mkpath("."); + QFile::copy(it.key(), it.value()); + } ++it; } notifyProgress((100 * ++counter) / m_importFiles.size(), progressTitle); @@ -461,6 +463,77 @@ bool ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; } +bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &iconFile, + const QString &iconSource) +{ + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + Model *model = doc ? doc->currentModel() : nullptr; + + if (model) { + PuppetCreator puppetCreator(doc->currentTarget(), model); + puppetCreator.createQml2PuppetExecutableIfMissing(); + QStringList puppetArgs; + puppetArgs << "--rendericon" << QString::number(size) << iconFile << iconSource; + QProcess *process = puppetCreator.createPuppetProcess( + "custom", {}, this, "", SLOT(processFinished(int, QProcess::ExitStatus)), puppetArgs); + + if (process->waitForStarted(5000)) { + connect(process, QOverload::of(&QProcess::finished), + process, &QProcess::deleteLater); + m_qmlPuppetProcesses << process; + return true; + } else { + delete process; + } + } + return false; +} + +void ItemLibraryAssetImporter::finalizeQuick3DImport() +{ + if (!isCancelled()) { + // Don't allow cancel anymore as existing asset overwrites are not trivially recoverable. + // Also, on Windows at least you can't delete a subdirectory of a watched directory, + // so complete rollback is no longer possible in any case. + emit importNearlyFinished(); + + copyImportedFiles(); + + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + Model *model = doc ? doc->currentModel() : nullptr; + if (model && !m_importFiles.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 an 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(); + } + } +} + bool ItemLibraryAssetImporter::isCancelled() const { keepUiAlive(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 67b36c6e72b..4bdccad6af9 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "import.h" @@ -72,6 +73,9 @@ signals: void importNearlyFinished() const; void importFinished(); +private slots: + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: void notifyFinished(); void reset(); @@ -83,6 +87,8 @@ private: void notifyProgress(int value, const QString &text) const; void keepUiAlive() const; bool confirmAssetOverwrite(const QString &assetName); + bool generateComponentIcon(int size, const QString &iconFile, const QString &iconSource); + void finalizeQuick3DImport(); #ifdef IMPORT_QUICK3D_ASSETS QScopedPointer m_quick3DAssetImporter; @@ -93,5 +99,7 @@ private: bool m_cancelled = false; QString m_importPath; QTemporaryDir *m_tempDir = nullptr; + QSet m_qmlPuppetProcesses; + int m_qmlPuppetCount = 0; }; } // QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index ba5b9a9f04d..f480e9bb97c 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -177,7 +177,8 @@ QProcess *PuppetCreator::createPuppetProcess(const QString &puppetMode, const QString &socketToken, QObject *handlerObject, const char *outputSlot, - const char *finishSlot) const + const char *finishSlot, + const QStringList &customOptions) const { return puppetProcess(qml2PuppetPath(m_availablePuppetType), qmlPuppetDirectory(m_availablePuppetType), @@ -185,7 +186,8 @@ QProcess *PuppetCreator::createPuppetProcess(const QString &puppetMode, socketToken, handlerObject, outputSlot, - finishSlot); + finishSlot, + customOptions); } @@ -195,7 +197,8 @@ QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, const QString &socketToken, QObject *handlerObject, const char *outputSlot, - const char *finishSlot) const + const char *finishSlot, + const QStringList &customOptions) const { auto puppetProcess = new QProcess; puppetProcess->setObjectName(puppetMode); @@ -228,7 +231,14 @@ QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, if (forceFreeType) forceFreeTypeOption = "-platform windows:fontengine=freetype"; - puppetProcess->start(puppetPath, {socketToken, puppetMode, "-graphicssystem raster", forceFreeTypeOption }); + if (puppetMode == "custom") { + QStringList args = customOptions; + args << "-graphicssystem raster"; + args << forceFreeTypeOption; + puppetProcess->start(puppetPath, args); + } else { + puppetProcess->start(puppetPath, {socketToken, puppetMode, "-graphicssystem raster", forceFreeTypeOption }); + } #ifndef QMLDESIGNER_TEST QString debugPuppet = m_designerSettings.value(DesignerSettingsKey:: diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h index d11a798a319..bafea8fa3e6 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h @@ -57,7 +57,8 @@ public: const QString &socketToken, QObject *handlerObject, const char *outputSlot, - const char *finishSlot) const; + const char *finishSlot, + const QStringList &customOptions = {}) const; void setQrcMappingString(const QString qrcMapping); @@ -87,7 +88,8 @@ protected: const QString &socketToken, QObject *handlerObject, const char *outputSlot, - const char *finishSlot) const; + const char *finishSlot, + const QStringList &customOptions) const; QProcessEnvironment processEnvironment() const; diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index da25ec53964..24a2b90351a 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -120,6 +120,12 @@ extend_qtc_executable(qml2puppet linegeometry.cpp linegeometry.h ) +extend_qtc_executable(qml2puppet + SOURCES_PREFIX "${SRCDIR}/qml2puppet/iconrenderer" + SOURCES + iconrenderer.cpp iconrenderer.h +) + extend_qtc_executable(qml2puppet SOURCES_PREFIX "${SRCDIR}/qml2puppet/instances" SOURCES diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 445ab2e867a..5ca88541b00 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -217,6 +217,8 @@ QtcTool { "editor3d/selectionboxgeometry.h", "editor3d/linegeometry.cpp", "editor3d/linegeometry.h", + "iconrenderer/iconrenderer.cpp", + "iconrenderer/iconrenderer.h", "qml2puppetmain.cpp", ] }