forked from qt-creator/qt-creator
QmlDesigner: Remove unused AssetExporterPlugin
Task-number: QDS-14877 Change-Id: I7e8a128f4f498067c3fbb4d6632d43e869393f76 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS
|
|||||||
Texteditor
|
Texteditor
|
||||||
UpdateInfo
|
UpdateInfo
|
||||||
VcsBase
|
VcsBase
|
||||||
assetexporterplugin
|
|
||||||
componentsplugin
|
componentsplugin
|
||||||
qmlpreviewplugin
|
qmlpreviewplugin
|
||||||
qtquickplugin)
|
qtquickplugin)
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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"
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
MetaInfo {
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/assetexporterplugin">
|
|
||||||
<file>assetexporterplugin.metainfo</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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";
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
Reference in New Issue
Block a user