QmlDesigner: Remove unused AssetExporterPlugin

Task-number: QDS-14877
Change-Id: I7e8a128f4f498067c3fbb4d6632d43e869393f76
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Tim Jenssen
2025-03-06 16:58:02 +01:00
parent e300842d4b
commit 56f02830cd
28 changed files with 0 additions and 2234 deletions

View File

@@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS
Texteditor Texteditor
UpdateInfo UpdateInfo
VcsBase VcsBase
assetexporterplugin
componentsplugin componentsplugin
qmlpreviewplugin qmlpreviewplugin
qtquickplugin) qtquickplugin)

View File

@@ -798,39 +798,6 @@ extend_qtc_plugin(QmlDesigner
dvconnector.cpp dvconnector.h dvconnector.cpp dvconnector.h
) )
add_qtc_plugin(assetexporterplugin
PLUGIN_CLASS AssetExporterPlugin
CONDITION TARGET QmlDesigner
PLUGIN_DEPENDS
Core ProjectExplorer QmlDesigner
DEPENDS Utils Qt::Qml Qt::QuickPrivate
PUBLIC_INCLUDES assetexporterplugin
PLUGIN_PATH ${QmlDesignerPluginInstallPrefix}
)
extend_qtc_plugin(assetexporterplugin
CONDITION ENABLE_COMPILE_WARNING_AS_ERROR
PROPERTIES COMPILE_WARNING_AS_ERROR ON
)
extend_qtc_plugin(assetexporterplugin
SOURCES_PREFIX assetexporterplugin
SOURCES
assetexportdialog.h assetexportdialog.cpp
assetexporter.h assetexporter.cpp
assetexporterplugin.h assetexporterplugin.cpp
assetexporterview.h assetexporterview.cpp
assetexportpluginconstants.h
componentexporter.h componentexporter.cpp
exportnotification.h exportnotification.cpp
filepathmodel.h filepathmodel.cpp
dumpers/assetnodedumper.h dumpers/assetnodedumper.cpp
dumpers/itemnodedumper.h dumpers/itemnodedumper.cpp
dumpers/nodedumper.h dumpers/nodedumper.cpp
dumpers/textnodedumper.h dumpers/textnodedumper.cpp
assetexporterplugin.qrc
)
add_qtc_plugin(componentsplugin add_qtc_plugin(componentsplugin
PLUGIN_CLASS ComponentsPlugin PLUGIN_CLASS ComponentsPlugin
CONDITION TARGET QmlDesigner CONDITION TARGET QmlDesigner

View File

@@ -1,228 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "assetexportdialog.h"
#include "../qmldesignertr.h"
#include "assetexportpluginconstants.h"
#include "filepathmodel.h"
#include <coreplugin/fileutils.h>
#include <coreplugin/icore.h>
#include <projectexplorer/task.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <utils/detailswidget.h>
#include <utils/layoutbuilder.h>
#include <utils/outputformatter.h>
#include <utils/pathchooser.h>
#include <QCheckBox>
#include <QPushButton>
#include <QListView>
#include <QPlainTextEdit>
#include <QDialogButtonBox>
#include <QScrollBar>
#include <QGridLayout>
#include <QProgressBar>
#include <QLabel>
#include <QStackedWidget>
#include <algorithm>
#include <cmath>
using namespace ProjectExplorer;
using namespace Utils;
namespace QmlDesigner {
static void addFormattedMessage(OutputFormatter *formatter, const QString &str, OutputFormat format)
{
if (!formatter)
return;
QPlainTextEdit *edit = formatter->plainTextEdit();
QScrollBar *scroll = edit->verticalScrollBar();
bool isAtBottom = scroll && scroll->value() == scroll->maximum();
QString msg = str + "\n";
formatter->appendMessage(msg, format);
if (isAtBottom)
scroll->setValue(scroll->maximum());
}
AssetExportDialog::AssetExportDialog(const FilePath &exportPath,
AssetExporter &assetExporter, FilePathModel &model,
QWidget *parent) :
QDialog(parent),
m_assetExporter(assetExporter),
m_filePathModel(model),
m_filesView(new QListView),
m_exportLogs(new QPlainTextEdit),
m_outputFormatter(new Utils::OutputFormatter())
{
resize(768, 480);
setWindowTitle(Tr::tr("Export Components"));
m_stackedWidget = new QStackedWidget;
m_exportProgress = new QProgressBar;
m_exportProgress->setRange(0,0);
auto optionsWidget = new QWidget;
auto advancedOptions = new DetailsWidget;
advancedOptions->setSummaryText(tr("Advanced Options"));
advancedOptions->setWidget(optionsWidget);
m_buttonBox = new QDialogButtonBox;
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close);
m_exportPath = new PathChooser;
m_exportPath->setExpectedKind(PathChooser::Kind::SaveFile);
m_exportPath->setFilePath(
exportPath.pathAppended(
ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata"
));
m_exportPath->setPromptDialogTitle(tr("Choose Export File"));
m_exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)"));
m_exportPath->lineEdit()->setReadOnly(true);
m_exportPath->addButton(tr("Open"), this, [this] {
Core::FileUtils::showInGraphicalShell(m_exportPath->filePath());
});
m_exportAssetsCheck = new QCheckBox(tr("Export assets"), this);
m_exportAssetsCheck->setChecked(true);
m_perComponentExportCheck = new QCheckBox(tr("Export components separately"), this);
m_perComponentExportCheck->setChecked(false);
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
m_stackedWidget->addWidget(m_filesView);
m_filesView->setModel(&m_filePathModel);
m_exportLogs->setReadOnly(true);
m_outputFormatter->setPlainTextEdit(m_exportLogs);
m_stackedWidget->addWidget(m_exportLogs);
switchView(false);
connect(m_buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this] {
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
m_assetExporter.cancel();
});
m_exportBtn = m_buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
m_exportBtn->setEnabled(false);
connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport);
connect(&m_filePathModel, &FilePathModel::modelReset, this, [this] {
m_exportProgress->setRange(0, 1000);
m_exportProgress->setValue(0);
m_exportBtn->setEnabled(true);
});
connect(m_buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this] {
close();
});
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
connect(&m_assetExporter, &AssetExporter::stateChanged,
this, &AssetExportDialog::onExportStateChanged);
connect(&m_assetExporter, &AssetExporter::exportProgressChanged,
this, &AssetExportDialog::updateExportProgress);
connect(&taskHub(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded);
using namespace Layouting;
Column {
m_exportAssetsCheck,
m_perComponentExportCheck,
st,
noMargin,
}.attachTo(optionsWidget);
Column {
Form { Tr::tr("Export path:"), m_exportPath },
advancedOptions,
m_stackedWidget,
m_exportProgress,
m_buttonBox,
}.attachTo(this);
}
AssetExportDialog::~AssetExportDialog()
{
m_assetExporter.cancel();
}
void AssetExportDialog::onExport()
{
switchView(true);
updateExportProgress(0.0);
TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT);
m_exportLogs->clear();
Utils::FilePath selectedPath = m_exportPath->filePath();
Utils::FilePath exportPath = m_perComponentExportCheck->isChecked() ?
(selectedPath.isDir() ? selectedPath : selectedPath.parentDir()) :
selectedPath;
m_assetExporter.exportQml(m_filePathModel.files(), exportPath,
m_exportAssetsCheck->isChecked(),
m_perComponentExportCheck->isChecked());
}
void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState)
{
switch (newState) {
case AssetExporter::ParsingState::ExportingDone:
m_exportBtn->setVisible(false);
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(true);
break;
default:
break;
}
m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone);
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy());
}
void AssetExportDialog::updateExportProgress(double value)
{
value = std::max(0.0, std::min(1.0, value));
m_exportProgress->setValue(std::round(value * 1000));
}
void AssetExportDialog::switchView(bool showExportView)
{
if (showExportView)
m_stackedWidget->setCurrentWidget(m_exportLogs);
else
m_stackedWidget->setCurrentWidget(m_filesView);
}
void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task)
{
Utils::OutputFormat format = Utils::NormalMessageFormat;
if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) {
switch (task.type) {
case ProjectExplorer::Task::Error:
format = Utils::StdErrFormat;
break;
case ProjectExplorer::Task::Warning:
format = Utils::StdOutFormat;
break;
default:
format = Utils::NormalMessageFormat;
}
addFormattedMessage(m_outputFormatter, task.description(), format);
}
}
}

View File

@@ -1,67 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "assetexporter.h"
#include <utils/filepath.h>
#include <QDialog>
#include <QStringListModel>
QT_BEGIN_NAMESPACE
class QPushButton;
class QCheckBox;
class QDialogButtonBox;
class QListView;
class QPlainTextEdit;
class QProgressBar;
class QStackedWidget;
QT_END_NAMESPACE
namespace Utils {
class OutputFormatter;
class PathChooser;
}
namespace ProjectExplorer {
class Task;
}
namespace QmlDesigner {
class FilePathModel;
class AssetExportDialog : public QDialog
{
Q_OBJECT
public:
explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter,
FilePathModel& model, QWidget *parent = nullptr);
~AssetExportDialog();
private:
void onExport();
void onExportStateChanged(AssetExporter::ParsingState newState);
void updateExportProgress(double value);
void switchView(bool showExportView);
void onTaskAdded(const ProjectExplorer::Task &task);
private:
AssetExporter &m_assetExporter;
FilePathModel &m_filePathModel;
QPushButton *m_exportBtn = nullptr;
QCheckBox *m_exportAssetsCheck = nullptr;
QCheckBox *m_perComponentExportCheck = nullptr;
QListView *m_filesView = nullptr;
QPlainTextEdit *m_exportLogs = nullptr;
Utils::OutputFormatter *m_outputFormatter = nullptr;
Utils::PathChooser *m_exportPath = nullptr;
QDialogButtonBox *m_buttonBox = nullptr;
QStackedWidget *m_stackedWidget = nullptr;
QProgressBar *m_exportProgress = nullptr;
};
} // QmlDesigner

View File

@@ -1,550 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "assetexporter.h"
#include "componentexporter.h"
#include "exportnotification.h"
#include <designdocument.h>
#include <modelutils.h>
#include <nodemetainfo.h>
#include <qmldesignerplugin.h>
#include <qmldesignertr.h>
#include <qmlitemnode.h>
#include <qmlobjectnode.h>
#include <rewriterview.h>
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <utils/async.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <auxiliarydataproperties.h>
#include <QCryptographicHash>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QLoggingCategory>
#include <QPlainTextEdit>
#include <QWaitCondition>
#include <random>
#include <queue>
using namespace ProjectExplorer;
using namespace std;
namespace {
bool makeParentPath(const Utils::FilePath &path)
{
QDir d;
return d.mkpath(path.toFileInfo().absolutePath());
}
QByteArray generateHash(const QString &token) {
static uint counter = 0;
std::mt19937 gen(std::random_device().operator()());
std::uniform_int_distribution<> distribution(1, 99999);
QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1();
return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
}
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg)
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg)
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg)
}
namespace QmlDesigner {
class AssetDumper
{
public:
AssetDumper();
~AssetDumper();
void dumpAsset(const QPixmap &p, const Utils::FilePath &path);
/* Keeps on dumping until all assets are dumped, then quits */
void quitDumper();
/* Aborts dumping */
void abortDumper();
private:
void addAsset(const QPixmap &p, const Utils::FilePath &path);
void doDumping(QPromise<void> &promise);
void savePixmap(const QPixmap &p, Utils::FilePath &path) const;
QFuture<void> m_dumpFuture;
QMutex m_queueMutex;
QWaitCondition m_queueCondition;
std::queue<std::pair<QPixmap, Utils::FilePath>> m_assets;
std::atomic<bool> m_quitDumper;
};
AssetExporter::AssetExporter(AssetExporterView *view,
ProjectExplorer::Project *project,
ProjectStorageDependencies projectStorageDependencies)
: m_currentState(*this)
, m_project(project)
, m_view(view)
, m_projectStorageDependencies{projectStorageDependencies}
{
connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded);
connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError);
}
AssetExporter::~AssetExporter()
{
cancel();
}
void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
bool exportAssets, bool perComponentExport)
{
m_perComponentExport = perComponentExport;
ExportNotification::addInfo(tr("Export root directory: %1.\nExporting assets: %2")
.arg(exportPath.isDir()
? exportPath.toUserOutput()
: exportPath.parentDir().toUserOutput())
.arg(exportAssets? tr("Yes") : tr("No")));
if (m_perComponentExport)
ExportNotification::addInfo(tr("Each component is exported separately."));
notifyProgress(0.0);
m_exportFiles = qmlFiles;
m_totalFileCount = m_exportFiles.count();
m_components.clear();
m_componentUuidCache.clear();
m_exportPath = exportPath.isDir() ? exportPath : exportPath.parentDir();
m_exportFile = exportPath.fileName();
m_currentState.change(ParsingState::Parsing);
if (exportAssets)
m_assetDumper = make_unique<AssetDumper>();
else
m_assetDumper.reset();
QTimer::singleShot(0, this, &AssetExporter::beginExport);
}
void AssetExporter::beginExport()
{
for (const Utils::FilePath &p : std::as_const(m_exportFiles)) {
if (m_cancelled)
break;
preprocessQmlFile(p);
}
if (!m_cancelled)
triggerLoadNextFile();
}
void AssetExporter::cancel()
{
if (!m_cancelled) {
ExportNotification::addInfo(tr("Canceling export."));
m_assetDumper.reset();
m_cancelled = true;
}
}
bool AssetExporter::isBusy() const
{
return m_currentState == AssetExporter::ParsingState::Parsing ||
m_currentState == AssetExporter::ParsingState::ExportingAssets ||
m_currentState == AssetExporter::ParsingState::WritingJson;
}
const QPixmap &AssetExporter::generateAsset(const ModelNode &node)
{
static QPixmap nullPixmap;
if (m_cancelled)
return nullPixmap;
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
QTC_ASSERT(!uuid.isEmpty(), return nullPixmap);
if (!m_assets.contains(uuid)) {
// Generate asset.
QmlObjectNode objectNode(node);
QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap();
m_assets[uuid] = asset;
}
return m_assets[uuid];
}
Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component,
const QString &suffix) const
{
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
if (!component || uuid.isEmpty())
return {};
const Utils::FilePath assetExportDir =
m_perComponentExport ? componentExportDir(component) : m_exportPath;
const Utils::FilePath assetPath = assetExportDir.pathAppended("assets")
.pathAppended(uuid + suffix + ".png");
return assetPath;
}
void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path)
{
if (m_cancelled || !m_assetDumper)
return;
m_assetDumper->dumpAsset(asset, path);
}
void AssetExporter::exportComponent(const ModelNode &rootNode)
{
qCDebug(loggerInfo) << "Exporting component" << rootNode.id();
m_components.push_back(make_unique<Component>(*this, rootNode));
m_components.back()->exportComponent();
}
void AssetExporter::notifyLoadError(AssetExporterView::LoadState state)
{
QString errorStr = tr("Unknown error.");
switch (state) {
case AssetExporterView::LoadState::Exausted:
errorStr = tr("Loading file is taking too long.");
break;
case AssetExporterView::LoadState::QmlErrorState:
errorStr = tr("Cannot parse. The file contains coding errors.");
break;
default:
return;
}
qCDebug(loggerError) << "QML load error:" << errorStr;
ExportNotification::addError(tr("Loading components failed. %1").arg(errorStr));
}
void AssetExporter::notifyProgress(double value) const
{
emit exportProgressChanged(value);
}
void AssetExporter::onQmlFileLoaded()
{
QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return);
qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl();
QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance()
->documentManager()
.currentDesignDocument();
if (designDocument->hasQmlParseErrors()) {
ExportNotification::addError(tr("Cannot export component. Document \"%1\" has parsing errors.")
.arg(designDocument->displayName()));
} else {
exportComponent(m_view->rootModelNode());
if (Utils::Result res = m_view->saveQmlFile(); !res) {
ExportNotification::addError(tr("Error saving component file. %1")
.arg(res.error().isEmpty()? tr("Unknown") : res.error()));
}
}
notifyProgress((m_totalFileCount - m_exportFiles.count()) * 0.8 / m_totalFileCount);
triggerLoadNextFile();
}
Utils::FilePath AssetExporter::componentExportDir(const Component *component) const
{
return m_exportPath.pathAppended(component->name());
}
void AssetExporter::preprocessQmlFile(const Utils::FilePath &path)
{
// Load the QML file and assign UUIDs to items having none.
// Meanwhile cache the Component UUIDs as well
#ifdef QDS_USE_PROJECTSTORAGE
ModelPointer model = Model::create(m_projectStorageDependencies,
"Item",
{Import::createLibraryImport("QtQuick")},
path.path());
#else
ModelPointer model = Model::create("Item", 2, 7);
#endif
Utils::FileReader reader;
if (!reader.fetch(path)) {
ExportNotification::addError(tr("Cannot preprocess file: %1. Error %2")
.arg(path.toUserOutput()).arg(reader.errorString()));
return;
}
QPlainTextEdit textEdit;
textEdit.setPlainText(QString::fromUtf8(reader.data()));
NotIndentingTextEditModifier *modifier = new NotIndentingTextEditModifier(textEdit.document());
modifier->setParent(model.get());
auto rewriterView = std::make_unique<RewriterView>(m_view->externalDependencies(),
QmlDesigner::RewriterView::Validate);
rewriterView->setCheckSemanticErrors(false);
rewriterView->setTextModifier(modifier);
model->attachView(rewriterView.get());
rewriterView->restoreAuxiliaryData();
ModelNode rootNode = rewriterView->rootModelNode();
if (!rootNode.isValid()) {
ExportNotification::addError(tr("Cannot preprocess file: %1").arg(path.toUrlishString()));
return;
}
if (assignUuids(rootNode)) {
// Some UUIDs were assigned. Rewrite the file.
rewriterView->writeAuxiliaryData();
const QByteArray data = textEdit.toPlainText().toUtf8();
Utils::FileSaver saver(path, QIODevice::Text);
saver.write(data);
if (!saver.finalize()) {
ExportNotification::addError(tr("Cannot update %1.\n%2")
.arg(path.toUserOutput()).arg(saver.errorString()));
return;
}
// Close the document if already open.
// UUIDS are changed and editor must reopen the document, otherwise stale state of the
// document is loaded.
for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) {
if (doc->filePath() == path) {
Core::EditorManager::closeDocuments({doc}, false);
break;
}
}
}
// Cache component UUID
const QString uuid = rootNode.auxiliaryDataWithDefault(uuidProperty).toString();
m_componentUuidCache[path.toUrlishString()] = uuid;
}
bool AssetExporter::assignUuids(const ModelNode &root)
{
// Assign an UUID to the node without one.
// Return true if an assignment takes place.
bool changed = false;
for (const ModelNode &node : root.allSubModelNodesAndThisNode()) {
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
if (uuid.isEmpty()) {
// Assign an unique identifier to the node.
QByteArray uuid = generateUuid(node);
node.setAuxiliaryData(uuidProperty, QString::fromLatin1(uuid));
changed = true;
}
}
return changed;
}
QByteArray AssetExporter::generateUuid(const ModelNode &node)
{
QByteArray uuid;
do {
uuid = generateHash(node.id());
} while (m_usedHashes.contains(uuid));
m_usedHashes.insert(uuid);
return uuid;
}
QString AssetExporter::componentUuid(const ModelNode &instance) const
{
// Returns the UUID of the component's root node
// Empty string is returned if the node is not an instance of a component within
// the project.
if (instance) {
const QString path = ModelUtils::componentFilePath(instance);
return m_componentUuidCache.value(path);
}
return {};
}
void AssetExporter::triggerLoadNextFile()
{
QTimer::singleShot(0, this, &AssetExporter::loadNextFile);
}
void AssetExporter::loadNextFile()
{
if (m_cancelled || m_exportFiles.isEmpty()) {
notifyProgress(0.8);
m_currentState.change(ParsingState::ParsingFinished);
writeMetadata();
return;
}
// Load the next pending file.
const Utils::FilePath file = m_exportFiles.takeFirst();
ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput()));
qCDebug(loggerInfo) << "Loading next file" << file;
m_view->loadQmlFile(file);
}
void AssetExporter::writeMetadata() const
{
if (m_cancelled) {
notifyProgress(1.0);
ExportNotification::addInfo(tr("Export canceled."));
m_currentState.change(ParsingState::ExportingDone);
return;
}
auto writeFile = [](const Utils::FilePath &path, const QJsonArray &artboards) {
if (!makeParentPath(path)) {
ExportNotification::addError(tr("Writing metadata failed. Cannot create file %1").
arg(path.toUrlishString()));
return;
}
ExportNotification::addInfo(tr("Writing metadata to file %1.").arg(path.toUserOutput()));
QJsonObject jsonRoot; // TODO: Write plugin info to root
jsonRoot.insert("artboards", artboards);
QJsonDocument doc(jsonRoot);
if (doc.isNull() || doc.isEmpty()) {
ExportNotification::addError(tr("Empty JSON document."));
return;
}
Utils::FileSaver saver(path, QIODevice::Text);
saver.write(doc.toJson(QJsonDocument::Indented));
if (!saver.finalize()) {
ExportNotification::addError(tr("Writing metadata failed. %1").
arg(saver.errorString()));
}
};
m_currentState.change(ParsingState::WritingJson);
auto const startupProject = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(startupProject, return);
const QString projectName = startupProject->displayName();
if (m_perComponentExport) {
for (auto &component : m_components) {
const Utils::FilePath path = componentExportDir(component.get());
writeFile(path.pathAppended(component->name() + ".metadata"), {component->json()});
}
} else {
QJsonArray artboards;
std::transform(m_components.cbegin(), m_components.cend(), back_inserter(artboards),
[](const unique_ptr<Component> &c) {return c->json(); });
writeFile(m_exportPath.pathAppended(m_exportFile), artboards);
}
notifyProgress(1.0);
ExportNotification::addInfo(tr("Export finished."));
if (m_assetDumper)
m_assetDumper->quitDumper();
m_currentState.change(ParsingState::ExportingDone);
}
AssetExporter::State::State(AssetExporter &exporter) :
m_assetExporter(exporter)
{
}
void AssetExporter::State::change(const ParsingState &state)
{
qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state;
if (m_state != state) {
m_state = state;
emit m_assetExporter.stateChanged(m_state);
}
}
QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s)
{
os << static_cast<std::underlying_type<QmlDesigner::AssetExporter::ParsingState>::type>(s);
return os;
}
AssetDumper::AssetDumper():
m_quitDumper(false)
{
m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this);
}
AssetDumper::~AssetDumper()
{
abortDumper();
}
void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path)
{
addAsset(p, path);
}
void AssetDumper::quitDumper()
{
m_quitDumper = true;
m_queueCondition.wakeAll();
if (!m_dumpFuture.isFinished())
m_dumpFuture.waitForFinished();
}
void AssetDumper::abortDumper()
{
if (!m_dumpFuture.isFinished()) {
m_dumpFuture.cancel();
m_queueCondition.wakeAll();
m_dumpFuture.waitForFinished();
}
}
void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path)
{
QMutexLocker locker(&m_queueMutex);
qDebug() << "Save Asset:" << path;
m_assets.push({p, path});
}
void AssetDumper::doDumping(QPromise<void> &promise)
{
auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) {
QMutexLocker locker(&m_queueMutex);
if (m_assets.empty())
return false;
*asset = m_assets.front();
m_assets.pop();
return true;
};
forever {
std::pair<QPixmap, Utils::FilePath> asset;
if (haveAsset(&asset)) {
if (promise.isCanceled())
break;
savePixmap(asset.first, asset.second);
} else {
if (m_quitDumper)
break;
QMutexLocker locker(&m_queueMutex);
m_queueCondition.wait(&m_queueMutex);
}
if (promise.isCanceled())
break;
}
}
void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const
{
if (p.isNull()) {
qCDebug(loggerWarn) << "Dumping null pixmap" << path;
return;
}
if (!makeParentPath(path)) {
ExportNotification::addError(Tr::tr("Error creating asset directory. %1").arg(path.fileName()));
return;
}
if (!p.save(path.toUrlishString())) {
ExportNotification::addError(Tr::tr("Error saving asset. %1").arg(path.fileName()));
}
}
}

View File

@@ -1,99 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "assetexporterview.h"
#include <QJsonObject>
#include <QSet>
#include <memory>
namespace ProjectExplorer { class Project; }
namespace QmlDesigner {
class AssetDumper;
class Component;
class AssetExporter : public QObject
{
Q_OBJECT
public:
enum class ParsingState {
Idle = 0,
Parsing,
ParsingFinished,
ExportingAssets,
ExportingAssetsFinished,
WritingJson,
ExportingDone
};
AssetExporter(AssetExporterView *view,
ProjectExplorer::Project *project,
ProjectStorageDependencies projectStorageDependencies);
~AssetExporter();
void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
bool exportAssets, bool perComponentExport);
void cancel();
bool isBusy() const;
const QPixmap &generateAsset(const ModelNode &node);
Utils::FilePath assetPath(const ModelNode &node, const Component *component,
const QString &suffix = {}) const;
void exportAsset(const QPixmap &asset, const Utils::FilePath &path);
QByteArray generateUuid(const ModelNode &node);
QString componentUuid(const ModelNode &instance) const;
signals:
void stateChanged(ParsingState);
void exportProgressChanged(double) const;
private:
ParsingState currentState() const { return m_currentState.m_state; }
void exportComponent(const ModelNode &rootNode);
void writeMetadata() const;
void notifyLoadError(AssetExporterView::LoadState state);
void notifyProgress(double value) const;
void triggerLoadNextFile();
void loadNextFile();
void onQmlFileLoaded();
Utils::FilePath componentExportDir(const Component *component) const;
void beginExport();
void preprocessQmlFile(const Utils::FilePath &path);
bool assignUuids(const ModelNode &root);
private:
mutable class State {
public:
State(AssetExporter&);
void change(const ParsingState &state);
operator ParsingState() const { return m_state; }
AssetExporter &m_assetExporter;
ParsingState m_state = ParsingState::Idle;
} m_currentState;
ProjectExplorer::Project *m_project = nullptr;
AssetExporterView *m_view = nullptr;
Utils::FilePaths m_exportFiles;
unsigned int m_totalFileCount = 0;
Utils::FilePath m_exportPath;
QString m_exportFile;
bool m_perComponentExport = false;
std::vector<std::unique_ptr<Component>> m_components;
QHash<QString, QString> m_componentUuidCache;
QSet<QByteArray> m_usedHashes;
QHash<QString, QPixmap> m_assets;
ProjectStorageDependencies m_projectStorageDependencies;
std::unique_ptr<AssetDumper> m_assetDumper;
bool m_cancelled = false;
};
QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s);
} // namespace QmlDesigner

View File

@@ -1,115 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "assetexporterplugin.h"
#include "assetexportdialog.h"
#include "assetexporter.h"
#include "assetexporterview.h"
#include "assetexportpluginconstants.h"
#include "componentexporter.h"
#include "filepathmodel.h"
#include <qmldesignerprojectmanager.h>
#include "dumpers/itemnodedumper.h"
#include "dumpers/textnodedumper.h"
#include "dumpers/assetnodedumper.h"
#include "coreplugin/actionmanager/actionmanager.h"
#include "coreplugin/actionmanager/actioncontainer.h"
#include "coreplugin/documentmanager.h"
#include "qmldesigner/qmldesignerplugin.h"
#include "projectexplorer/projectexplorerconstants.h"
#include "projectexplorer/projectmanager.h"
#include "projectexplorer/project.h"
#include "projectexplorer/projectmanager.h"
#include "projectexplorer/taskhub.h"
#include "extensionsystem/pluginmanager.h"
#include "extensionsystem/pluginspec.h"
#include "utils/algorithm.h"
#include <QCoreApplication>
#include <QAction>
#include <QLoggingCategory>
namespace QmlDesigner {
AssetExporterPlugin::AssetExporterPlugin()
: m_projectManager{QmlDesigner::QmlDesignerPlugin::projectManagerForPluginInitializationOnly()}
{
ProjectExplorer::TaskHub::addCategory({Constants::TASK_CATEGORY_ASSET_EXPORT,
tr("Asset Export"),
tr("Issues with exporting assets."),
false});
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager();
m_view = viewManager.registerView(std::make_unique<AssetExporterView>(
designerPlugin->externalDependenciesForPluginInitializationOnly()));
// Add dumper templates for factory instantiation.
Component::addNodeDumper<ItemNodeDumper>();
Component::addNodeDumper<TextNodeDumper>();
Component::addNodeDumper<AssetNodeDumper>();
// Instantiate actions created by the plugin.
addActions();
connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
this, &AssetExporterPlugin::updateActions);
updateActions();
}
QString AssetExporterPlugin::pluginName() const
{
return QLatin1String("AssetExporterPlugin");
}
void AssetExporterPlugin::onExport()
{
auto startupProject = ProjectExplorer::ProjectManager::startupProject();
if (!startupProject)
return;
FilePathModel model(startupProject);
auto exportDir = startupProject->projectFilePath().parentDir();
if (!exportDir.parentDir().isEmpty())
exportDir = exportDir.parentDir();
exportDir = exportDir.pathAppended(startupProject->displayName() + "_export");
AssetExporter assetExporter(m_view, startupProject, m_projectManager.projectStorageDependencies());
AssetExportDialog assetExporterDialog(exportDir, assetExporter, model);
assetExporterDialog.exec();
}
void AssetExporterPlugin::addActions()
{
auto exportAction = new QAction(tr("Export Components"), this);
exportAction->setToolTip(tr("Export components in the current project."));
connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport);
Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML);
// Add action to build menu
Core::ActionContainer *buildMenu =
Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN);
}
void AssetExporterPlugin::updateActions()
{
auto project = ProjectExplorer::ProjectManager::startupProject();
QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action();
exportAction->setEnabled(project && !project->needsConfiguration());
}
QString AssetExporterPlugin::metaInfo() const
{
return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo");
}
} //QmlDesigner

View File

@@ -1,35 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <iwidgetplugin.h>
namespace QmlDesigner {
class AssetExporter;
class AssetExporterView;
class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json")
Q_DISABLE_COPY(AssetExporterPlugin)
Q_INTERFACES(QmlDesigner::IWidgetPlugin)
public:
AssetExporterPlugin();
QString metaInfo() const final;
QString pluginName() const final;
private:
void onExport();
void addActions();
void updateActions();
AssetExporterView *m_view = nullptr;
class QmlDesignerProjectManager &m_projectManager;
};
} // namespace QmlDesigner

View File

@@ -1,6 +0,0 @@
{
"Vendor" : "The Qt Company Ltd",
"Category" : "Qt Quick",
"Description" : "Plugin for exporting assets and QML from QmlDesigner",
"Url" : "http://www.qt.io"
}

View File

@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/assetexporterplugin">
<file>assetexporterplugin.metainfo</file>
</qresource>
</RCC>

View File

@@ -1,142 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "assetexporterview.h"
#include "qmlitemnode.h"
#include "rewriterview.h"
#include "coreplugin/editormanager/editormanager.h"
#include "coreplugin/editormanager/ieditor.h"
#include "coreplugin/modemanager.h"
#include "coreplugin/coreconstants.h"
#include <QLoggingCategory>
namespace {
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg)
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg)
//Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg)
const int RetryIntervalMs = 500;
const int MinRetry = 2;
}
namespace QmlDesigner {
AssetExporterView::AssetExporterView(ExternalDependenciesInterface &externalDependencies)
: AbstractView(externalDependencies)
, m_timer(this)
{
m_timer.setInterval(RetryIntervalMs);
// We periodically check if file is loaded.
connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout);
}
bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs)
{
qCDebug(loggerInfo) << "Load file" << path;
if (loadingState() == LoadState::Busy)
return false;
setState(LoadState::Busy);
m_retryCount = std::max(MinRetry, static_cast<int>((timeoutSecs * 1000) / RetryIntervalMs));
m_currentEditor = Core::EditorManager::openEditor(path, Utils::Id(),
Core::EditorManager::DoNotMakeVisible);
Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN);
Core::ModeManager::setFocusToCurrentMode();
m_timer.start();
return true;
}
Utils::Result AssetExporterView::saveQmlFile() const
{
if (!m_currentEditor) {
qCDebug(loggerWarn) << "Saving QML file failed. No editor.";
return Utils::Result::Error("Saving QML file failed. No editor.");
}
return m_currentEditor->document()->save();
}
void AssetExporterView::modelAttached(Model *model)
{
if (model->rewriterView() && !model->rewriterView()->errors().isEmpty())
setState(LoadState::QmlErrorState);
AbstractView::modelAttached(model);
}
void AssetExporterView::
instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
{
if (inErrorState() || loadingState() == LoadState::Loaded)
return; // Already reached error or connected state.
// We expect correct dimensions are available if the rootnode's
// information change message is received.
const auto nodes = informationChangeHash.keys();
bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) {
return n.isRootNode();
});
if (hasRootNode)
handleMaybeDone();
}
void AssetExporterView::instancesPreviewImageChanged([[maybe_unused]] const QVector<ModelNode> &nodeList)
{
emit previewChanged();
}
bool AssetExporterView::inErrorState() const
{
return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState;
}
bool AssetExporterView::isLoaded() const
{
return isAttached() && QmlItemNode(rootModelNode()).isValid();
}
void AssetExporterView::setState(AssetExporterView::LoadState state)
{
if (state != m_state) {
m_state = state;
qCDebug(loggerInfo) << "Loading state changed" << m_state;
if (inErrorState() || m_state == LoadState::Loaded) {
m_timer.stop();
// TODO: Send the loaded signal with a delay. The assumption that model attached and a
// valid root object is enough to declare a QML file is ready is incorrect. A ideal
// solution would be that the QML Puppet notifies file ready signal.
if (m_state == LoadState::Loaded)
QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished);
else
emit loadingError(m_state);
}
}
}
void AssetExporterView::handleMaybeDone()
{
if (isLoaded())
setState(LoadState::Loaded);
}
void AssetExporterView::handleTimerTimeout()
{
if (!inErrorState() && loadingState() != LoadState::Loaded)
handleMaybeDone();
if (--m_retryCount < 0)
setState(LoadState::Exausted);
}
}
QT_BEGIN_NAMESPACE
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s)
{
os << static_cast<std::underlying_type<QmlDesigner::AssetExporterView::LoadState>::type>(s);
return os;
}
QT_END_NAMESPACE

View File

@@ -1,62 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "abstractview.h"
#include <utils/filepath.h>
#include <QObject>
#include <QTimer>
namespace Core { class IEditor; }
namespace QmlDesigner {
class AssetExporterView : public AbstractView
{
Q_OBJECT
public:
enum class LoadState {
Idle = 1,
Busy,
Exausted,
QmlErrorState,
Loaded
};
AssetExporterView(ExternalDependenciesInterface &externalDependencies);
bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10);
Utils::Result saveQmlFile() const;
void modelAttached(Model *model) override;
void instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash) override;
void instancesPreviewImageChanged(const QVector<ModelNode> &nodeList) override;
LoadState loadingState() const { return m_state; }
bool inErrorState() const;
signals:
void loadingFinished();
void loadingError(LoadState);
void previewChanged();
private:
bool isLoaded() const;
void setState(LoadState state);
void handleMaybeDone();
void handleTimerTimeout();
Core::IEditor *m_currentEditor = nullptr;
QTimer m_timer;
int m_retryCount = 0;
LoadState m_state = LoadState::Idle;
bool m_waitForPuppet = false;
};
}
QT_BEGIN_NAMESPACE
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s);
QT_END_NAMESPACE

View File

@@ -1,66 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <auxiliarydata.h>
namespace QmlDesigner {
namespace Constants {
const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml";
const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export";
//***************************************************************************
// Metadata tags
//***************************************************************************
// Plugin info tags
const char PluginInfoTag[] = "pluginInfo";
const char MetadataVersionTag[] = "metadataVersion";
const char DocumentInfoTag[] = "documentInfo";
const char DocumentNameTag[] = "name";
// Layer data tags
const char ArtboardListTag[] = "artboards";
const char NameTag[] = "name";
const char XPosTag[] = "x";
const char YPosTag[] = "y";
const char WidthTag[] = "width";
const char HeightTag[] = "height";
const char MetadataTag[] = "metadata";
const char ChildrenTag[] = "children";
const char CustomIdTag[] = "customId";
const char QmlIdTag[] = "qmlId";
const char ExportTypeTag[] = "exportType";
const char ExportTypeComponent[] = "component";
const char ExportTypeChild[] = "child";
const char QmlPropertiesTag[] = "qmlProperties";
const char ImportsTag[] = "extraImports";
const char UuidTag[] = "uuid";
const char ClipTag[] = "clip";
const char AssetDataTag[] = "assetData";
const char ReferenceAssetTag[] = "referenceAsset";
const char AssetPathTag[] = "assetPath";
const char AssetBoundsTag[] = "assetBounds";
const char OpacityTag[] = "opacity";
const char TypeNameTag[] = "typeName";
const char TypeIdTag[] = "typeId";
const char TextDetailsTag[] = "textDetails";
const char FontFamilyTag[] = "fontFamily";
const char FontSizeTag[] = "fontSize";
const char FontStyleTag[] = "fontStyle";
const char LetterSpacingTag[] = "kerning";
const char TextColorTag[] = "textColor";
const char TextContentTag[] = "contents";
const char IsMultilineTag[] = "multiline";
const char LineHeightTag[] = "lineHeight";
const char HAlignTag[] = "horizontalAlignment";
const char VAlignTag[] = "verticalAlignment";
}
}

View File

@@ -1,158 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "componentexporter.h"
#include "assetexporter.h"
#include "assetexportpluginconstants.h"
#include "exportnotification.h"
#include "dumpers/nodedumper.h"
#include "model.h"
#include "nodeabstractproperty.h"
#include "nodemetainfo.h"
#include "qmlitemnode.h"
#include "rewriterview.h"
#include "utils/qtcassert.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QPainter>
namespace {
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
}
namespace QmlDesigner {
using namespace Constants;
std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> Component::m_readers;
Component::Component(AssetExporter &exporter, const ModelNode &rootNode):
m_exporter(exporter),
m_rootNode(rootNode)
{
m_name = m_rootNode.id();
if (m_name.isEmpty())
m_name = QString::fromUtf8(m_rootNode.type());
}
QJsonObject Component::json() const
{
return m_json;
}
AssetExporter &Component::exporter()
{
return m_exporter;
}
void Component::exportComponent()
{
QTC_ASSERT(m_rootNode.isValid(), return);
m_json = nodeToJson(m_rootNode);
// Change the export type to component
QJsonObject metadata = m_json.value(MetadataTag).toObject();
metadata.insert(ExportTypeTag, ExportTypeComponent);
addReferenceAsset(metadata);
m_json.insert(MetadataTag, metadata);
addImports();
}
const QString &Component::name() const
{
return m_name;
}
NodeDumper *Component::createNodeDumper(const ModelNode &node) const
{
std::unique_ptr<NodeDumper> reader;
for (auto &dumperCreator: m_readers) {
std::unique_ptr<NodeDumper> r(dumperCreator->instance(node));
if (r->isExportable()) {
if (reader) {
if (reader->priority() < r->priority())
reader = std::move(r);
} else {
reader = std::move(r);
}
}
}
if (!reader)
qCDebug(loggerInfo()) << "No dumper for node" << node;
return reader.release();
}
QJsonObject Component::nodeToJson(const ModelNode &node)
{
QJsonObject jsonObject;
// Don't export States, Connection, Timeline etc nodes.
if (!node.metaInfo().isQtQuickItem())
return {};
std::unique_ptr<NodeDumper> dumper(createNodeDumper(node));
if (dumper) {
jsonObject = dumper->json(*this);
} else {
ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.")
.arg(node.id()).arg(QString::fromUtf8(node.type())));
}
QJsonArray children;
for (const ModelNode &childnode : node.directSubModelNodes()) {
const QJsonObject childJson = nodeToJson(childnode);
if (!childJson.isEmpty())
children.append(childJson);
}
if (!children.isEmpty())
jsonObject.insert(ChildrenTag, children);
return jsonObject;
}
void Component::addReferenceAsset(QJsonObject &metadataObject) const
{
QPixmap refAsset = m_exporter.generateAsset(m_rootNode);
stichChildrendAssets(m_rootNode, refAsset);
Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref");
m_exporter.exportAsset(refAsset, refAssetPath);
QJsonObject assetData;
if (metadataObject.contains(AssetDataTag))
assetData = metadataObject[AssetDataTag].toObject();
assetData.insert(ReferenceAssetTag, refAssetPath.toUrlishString());
metadataObject.insert(AssetDataTag, assetData);
}
void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const
{
if (!node.hasAnySubModelNodes())
return;
QPainter painter(&parentPixmap);
for (const ModelNode &child : node.directSubModelNodes()) {
QPixmap childPixmap = m_exporter.generateAsset(child);
if (childPixmap.isNull())
continue;
stichChildrendAssets(child, childPixmap);
QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform();
painter.setTransform(cTransform);
painter.drawPixmap(QPoint(0, 0), childPixmap);
}
painter.end();
}
void Component::addImports()
{
QJsonArray importsArray;
for (const Import &import : m_rootNode.model()->imports())
importsArray.append(import.toString());
if (!importsArray.empty())
m_json.insert(Constants::ImportsTag, importsArray);
}
} // namespace QmlDesigner

View File

@@ -1,78 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QByteArrayList>
#include <QCoreApplication>
#include <QJsonObject>
#include <memory>
#include <vector>
#include "utils/qtcassert.h"
QT_BEGIN_NAMESPACE
class QJsonArray;
QT_END_NAMESPACE
namespace QmlDesigner {
class AssetExporter;
class ModelNode;
class Component;
class NodeDumper;
namespace Internal {
class NodeDumperCreatorBase
{
public:
virtual ~NodeDumperCreatorBase() {}
protected:
virtual NodeDumper *instance(const ModelNode &) const = 0;
friend Component;
};
template<class T>
class NodeDumperCreator : public NodeDumperCreatorBase
{
public:
NodeDumperCreator() = default;
~NodeDumperCreator() = default;
protected:
NodeDumper *instance(const ModelNode &node) const { return new T(node); }
};
} //Internal
class Component
{
Q_DECLARE_TR_FUNCTIONS(Component);
public:
Component(AssetExporter& exporter, const ModelNode &rootNode);
void exportComponent();
const QString &name() const;
QJsonObject json() const;
AssetExporter &exporter();
template<typename T> static void addNodeDumper()
{
QTC_ASSERT((std::is_base_of<NodeDumper, T>::value), return);
m_readers.push_back(std::make_unique<Internal::NodeDumperCreator<T>>());
}
private:
NodeDumper* createNodeDumper(const ModelNode &node) const;
QJsonObject nodeToJson(const ModelNode &node);
void addReferenceAsset(QJsonObject &metadataObject) const;
void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const;
void addImports();
private:
AssetExporter& m_exporter;
const ModelNode &m_rootNode;
QString m_name;
QJsonObject m_json;
static std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> m_readers;
};
}

View File

@@ -1,47 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "assetnodedumper.h"
#include "assetexportpluginconstants.h"
#include "assetexporter.h"
#include "qmlitemnode.h"
#include "componentexporter.h"
#include "utils/fileutils.h"
#include <QPixmap>
namespace QmlDesigner {
using namespace Constants;
AssetNodeDumper::AssetNodeDumper(const ModelNode &node)
: ItemNodeDumper(node)
{
}
bool AssetNodeDumper::isExportable() const
{
auto qtQuickImageMetaInfo = model()->qtQuickImageMetaInfo();
auto qtQuickRectangleMetaInfo = model()->qtQuickRectangleMetaInfo();
return metaInfo().isBasedOn(qtQuickImageMetaInfo, qtQuickRectangleMetaInfo);
}
QJsonObject AssetNodeDumper::json(Component &component) const
{
QJsonObject jsonObject = ItemNodeDumper::json(component);
AssetExporter &exporter = component.exporter();
const Utils::FilePath assetPath = exporter.assetPath(m_node, &component);
exporter.exportAsset(exporter.generateAsset(m_node), assetPath);
QJsonObject assetData;
assetData.insert(AssetPathTag, assetPath.toUrlishString());
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
metadata.insert(AssetDataTag, assetData);
jsonObject.insert(MetadataTag, metadata);
return jsonObject;
}
}

View File

@@ -1,20 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "itemnodedumper.h"
namespace QmlDesigner {
class Component;
class AssetNodeDumper : public ItemNodeDumper
{
public:
AssetNodeDumper(const ModelNode &node);
~AssetNodeDumper() override = default;
bool isExportable() const override;
int priority() const override { return 200; }
QJsonObject json(Component &component) const override;
};
}

View File

@@ -1,76 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "itemnodedumper.h"
#include "assetexportpluginconstants.h"
#include "assetexporter.h"
#include "componentexporter.h"
#include "qmlitemnode.h"
#include "annotation.h"
namespace {
static QString capitalize(const QString &str)
{
if (str.isEmpty())
return {};
QString tmp = str;
tmp[0] = QChar(str[0]).toUpper().toLatin1();
return tmp;
}
}
namespace QmlDesigner {
using namespace Constants;
ItemNodeDumper::ItemNodeDumper(const ModelNode &node)
: NodeDumper(node)
{
}
bool QmlDesigner::ItemNodeDumper::isExportable() const
{
return metaInfo().isQtQuickItem();
}
QJsonObject QmlDesigner::ItemNodeDumper::json([[maybe_unused]] QmlDesigner::Component &component) const
{
const QmlObjectNode &qmlObjectNode = objectNode();
QJsonObject jsonObject;
const QString qmlId = qmlObjectNode.id();
QString name = m_node.simplifiedTypeName();
if (!qmlId.isEmpty())
name.append("_" + capitalize(qmlId));
jsonObject.insert(NameTag, name);
// Position relative to parent
QmlItemNode itemNode = qmlObjectNode.toQmlItemNode();
QPointF pos = itemNode.instancePosition();
jsonObject.insert(XPosTag, pos.x());
jsonObject.insert(YPosTag, pos.y());
// size
QSizeF size = itemNode.instanceSize();
jsonObject.insert(WidthTag, size.width());
jsonObject.insert(HeightTag, size.height());
QJsonObject metadata;
metadata.insert(QmlIdTag, qmlId);
metadata.insert(UuidTag, uuid());
metadata.insert(ExportTypeTag, ExportTypeChild);
metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type()));
if (m_node.hasCustomId())
metadata.insert(CustomIdTag, m_node.customId());
QString typeId = component.exporter().componentUuid(m_node);
if (!typeId.isEmpty())
metadata.insert(TypeIdTag, typeId);
jsonObject.insert(MetadataTag, metadata);
return jsonObject;
}
}

View File

@@ -1,22 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "nodedumper.h"
namespace QmlDesigner {
class ModelNode;
class Component;
class ItemNodeDumper : public NodeDumper
{
public:
ItemNodeDumper(const ModelNode &node);
~ItemNodeDumper() override = default;
int priority() const override { return 100; }
bool isExportable() const override;
QJsonObject json(Component &component) const override;
};
}

View File

@@ -1,28 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "nodedumper.h"
#include "assetexportpluginconstants.h"
#include <auxiliarydataproperties.h>
namespace QmlDesigner {
NodeDumper::NodeDumper(const ModelNode &node)
: m_node(node)
, m_objectNode(node)
, m_metaInfo(node.metaInfo())
, m_model{node.model()}
{
}
QVariant NodeDumper::propertyValue(const PropertyName &name) const
{
return m_objectNode.instanceValue(name);
}
QString NodeDumper::uuid() const
{
return m_node.auxiliaryDataWithDefault(uuidProperty).toString();
}
}

View File

@@ -1,40 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmlobjectnode.h"
#include <QJsonObject>
#include <QByteArrayList>
namespace QmlDesigner {
class Component;
class ModelNode;
class NodeDumper
{
public:
NodeDumper(const ModelNode &node);
virtual ~NodeDumper() = default;
virtual int priority() const = 0;
virtual bool isExportable() const = 0;
virtual QJsonObject json(Component& component) const = 0;
const NodeMetaInfo &metaInfo() const { return m_metaInfo; }
const QmlObjectNode& objectNode() const { return m_objectNode; }
QVariant propertyValue(const PropertyName &name) const;
QString uuid() const;
Model *model() const { return m_model; }
protected:
const ModelNode &m_node;
private:
QmlObjectNode m_objectNode;
NodeMetaInfo m_metaInfo;
Model *m_model = nullptr;
};
}

View File

@@ -1,90 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "textnodedumper.h"
#include "assetexportpluginconstants.h"
#include <model.h>
#include <QColor>
#include <QFontInfo>
#include <QFontMetricsF>
#include <QHash>
#include <QtMath>
#include <private/qquicktext_p.h>
#include <algorithm>
namespace {
const QHash<QString, QString> AlignMapping{
{"AlignRight", "RIGHT"},
{"AlignHCenter", "CENTER"},
{"AlignJustify", "JUSTIFIED"},
{"AlignLeft", "LEFT"},
{"AlignTop", "TOP"},
{"AlignVCenter", "CENTER"},
{"AlignBottom", "BOTTOM"}
};
QString toJsonAlignEnum(QString value) {
if (value.isEmpty() || !AlignMapping.contains(value))
return {};
return AlignMapping[value];
}
}
namespace QmlDesigner {
using namespace Constants;
TextNodeDumper::TextNodeDumper(const ModelNode &node)
: ItemNodeDumper(node)
{
}
bool TextNodeDumper::isExportable() const
{
auto qtQuickTextMetaInfo = model()->qtQuickTextMetaInfo();
auto qtQuickControlsLabelMetaInfo = model()->qtQuickControlsLabelMetaInfo();
return metaInfo().isBasedOn(qtQuickTextMetaInfo, qtQuickControlsLabelMetaInfo);
}
QJsonObject TextNodeDumper::json([[maybe_unused]] Component &component) const
{
QJsonObject jsonObject = ItemNodeDumper::json(component);
QJsonObject textDetails;
textDetails.insert(TextContentTag, propertyValue("text").toString());
QFont font = propertyValue("font").value<QFont>();
QFontInfo fontInfo(font);
textDetails.insert(FontFamilyTag, fontInfo.family());
textDetails.insert(FontStyleTag, fontInfo.styleName());
textDetails.insert(FontSizeTag, fontInfo.pixelSize());
textDetails.insert(LetterSpacingTag, font.letterSpacing());
QColor fontColor(propertyValue("font.color").toString());
textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb));
textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString()));
textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString()));
textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0);
// Calculate line height in pixels
QFontMetricsF fm(font);
auto lineHeightMode = propertyValue("lineHeightMode").value<QQuickText::LineHeightMode>();
double lineHeight = propertyValue("lineHeight").toDouble();
qreal lineHeightPx = (lineHeightMode == QQuickText::FixedHeight) ?
lineHeight : qCeil(fm.height()) * lineHeight;
textDetails.insert(LineHeightTag, lineHeightPx);
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
metadata.insert(TextDetailsTag, textDetails);
jsonObject.insert(MetadataTag, metadata);
return jsonObject;
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "itemnodedumper.h"
namespace QmlDesigner {
class Component;
class TextNodeDumper : public ItemNodeDumper
{
public:
TextNodeDumper(const ModelNode &node);
~TextNodeDumper() override = default;
bool isExportable() const override;
int priority() const override { return 200; }
QJsonObject json(Component &component) const override;
};
}

View File

@@ -1,41 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "exportnotification.h"
#include "assetexportpluginconstants.h"
#include "projectexplorer/taskhub.h"
#include <QLoggingCategory>
namespace {
Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg)
}
using namespace ProjectExplorer;
namespace {
static void addTask(Task::TaskType type, const QString &desc)
{
qCDebug(loggerDebug) << desc;
Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT);
TaskHub::addTask(task);
}
}
namespace QmlDesigner {
void ExportNotification::addError(const QString &errMsg)
{
addTask(Task::Error, errMsg);
}
void ExportNotification::addWarning(const QString &warningMsg)
{
addTask(Task::Warning, warningMsg);
}
void ExportNotification::addInfo(const QString &infoMsg)
{
addTask(Task::Unknown, infoMsg);
}
} // QmlDesigner

View File

@@ -1,16 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QString>
namespace QmlDesigner {
class ExportNotification
{
public:
static void addError(const QString &errMsg);
static void addWarning(const QString &warningMsg);
static void addInfo(const QString &infoMsg);
};
} // QmlDesigner

View File

@@ -1,143 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "filepathmodel.h"
#include "exportnotification.h"
#include <projectexplorer/project.h>
#include <projectexplorer/projectnodes.h>
#include <utils/async.h>
#include <QLoggingCategory>
#include <QTimer>
using namespace ProjectExplorer;
namespace {
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg)
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg)
void findQmlFiles(QPromise<Utils::FilePath> &promise, const Project *project)
{
if (!project || promise.isCanceled())
return;
int index = 0;
project->files([&promise, &index](const Node* node) ->bool {
if (promise.isCanceled())
return false;
Utils::FilePath path = node->filePath();
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
if (isComponent && node->filePath().endsWith(".ui.qml"))
promise.addResult(path, index++);
return true;
});
}
}
namespace QmlDesigner {
FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent)
: QAbstractListModel(parent),
m_project(project)
{
QTimer::singleShot(0, this, &FilePathModel::processProject);
}
FilePathModel::~FilePathModel()
{
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
!m_preprocessWatcher->isFinished()) {
ExportNotification::addInfo(tr("Canceling file preparation."));
m_preprocessWatcher->cancel();
m_preprocessWatcher->waitForFinished();
qCDebug(loggerInfo) << "Canceled file preparation.";
}
}
Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags itemFlags = QAbstractListModel::flags(index);
if (index.isValid())
itemFlags |= Qt::ItemIsUserCheckable;
return itemFlags;
}
int FilePathModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return m_files.size();
return 0;
}
QVariant FilePathModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return {};
switch (role) {
case Qt::DisplayRole:
return m_files[index.row()].toUserOutput();
case Qt::CheckStateRole:
return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked;
default:
break;
}
return {};
}
bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || role != Qt::CheckStateRole)
return false;
const Utils::FilePath path = m_files[index.row()];
if (value == Qt::Checked)
m_skipped.erase(path);
else
m_skipped.insert(path);
emit dataChanged(index, index);
return true;
}
Utils::FilePaths FilePathModel::files() const
{
Utils::FilePaths selectedPaths;
std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths),
[this](const Utils::FilePath &path) {
return !m_skipped.count(path);
});
return selectedPaths;
}
void FilePathModel::processProject()
{
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
!m_preprocessWatcher->isFinished()) {
qCDebug(loggerError) << "Previous model load not finished.";
return;
}
beginResetModel();
m_preprocessWatcher.reset(new QFutureWatcher<Utils::FilePath>(this));
connect(m_preprocessWatcher.get(),
&QFutureWatcher<Utils::FilePath>::resultReadyAt,
this,
[this](int resultIndex) {
beginInsertRows(index(0, 0), m_files.size(), m_files.size());
m_files.append(m_preprocessWatcher->resultAt(resultIndex));
endInsertRows();
});
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
this, &FilePathModel::endResetModel);
QFuture<Utils::FilePath> f = Utils::asyncRun(&findQmlFiles, m_project);
m_preprocessWatcher->setFuture(f);
}
}

View File

@@ -1,43 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/filepath.h>
#include <QAbstractListModel>
#include <QCoreApplication>
#include <QFutureWatcher>
#include <memory>
#include <unordered_set>
namespace ProjectExplorer {
class Project;
}
namespace QmlDesigner {
class FilePathModel : public QAbstractListModel
{
Q_DECLARE_TR_FUNCTIONS(QmlDesigner::FilePathModel)
public:
FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr);
~FilePathModel() override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Utils::FilePaths files() const;
private:
void processProject();
ProjectExplorer::Project *m_project = nullptr;
std::unique_ptr<QFutureWatcher<Utils::FilePath>> m_preprocessWatcher;
std::unordered_set<Utils::FilePath> m_skipped;
Utils::FilePaths m_files;
};
}