AssetExporter: Let user select files to export

Refine asset exporter dialog. Add progress bar, export notification,
path chooser and other small UI changes

Task-number: QDS-1560
Change-Id: Iaba61575581171e7e1495ebfaf2e345546fb7400
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Vikas Pachdha
2020-06-09 00:23:11 +02:00
parent 7f1b286264
commit 1b6a0a290b
11 changed files with 411 additions and 125 deletions

View File

@@ -51,6 +51,7 @@ add_qtc_plugin(assetexporterplugin
assetexporterplugin/assetexportpluginconstants.h assetexporterplugin/assetexportpluginconstants.h
assetexporterplugin/componentexporter.h assetexporterplugin/componentexporter.cpp assetexporterplugin/componentexporter.h assetexporterplugin/componentexporter.cpp
assetexporterplugin/exportnotification.h assetexporterplugin/exportnotification.cpp assetexporterplugin/exportnotification.h assetexporterplugin/exportnotification.cpp
assetexporterplugin/filepathmodel.h assetexporterplugin/filepathmodel.cpp
assetexporterplugin/parsers/modelitemnodeparser.h assetexporterplugin/parsers/modelitemnodeparser.cpp assetexporterplugin/parsers/modelitemnodeparser.h assetexporterplugin/parsers/modelitemnodeparser.cpp
assetexporterplugin/parsers/modelnodeparser.h assetexporterplugin/parsers/modelnodeparser.cpp assetexporterplugin/parsers/modelnodeparser.h assetexporterplugin/parsers/modelnodeparser.cpp
assetexporterplugin/assetexporterplugin.qrc assetexporterplugin/assetexporterplugin.qrc

View File

@@ -23,44 +23,97 @@
** **
****************************************************************************/ ****************************************************************************/
#include "assetexportdialog.h" #include "assetexportdialog.h"
#include "ui_assetexportdialog.h"
#include "ui_assetexportdialog.h"
#include "assetexportpluginconstants.h"
#include "filepathmodel.h"
#include "projectexplorer/task.h"
#include "projectexplorer/taskhub.h"
#include "utils/fileutils.h" #include "utils/fileutils.h"
#include "utils/outputformatter.h"
#include <QPushButton> #include <QPushButton>
#include <QListView>
#include <QPlainTextEdit>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QMessageBox> #include <QMessageBox>
#include <QScrollBar>
#include <algorithm> #include <algorithm>
namespace QmlDesigner { namespace {
static void addFormattedMessage(Utils::OutputFormatter *formatter, const QString &str,
Utils::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());
}
}
using namespace ProjectExplorer;
namespace QmlDesigner {
AssetExportDialog::AssetExportDialog(const Utils::FilePath &exportPath, AssetExportDialog::AssetExportDialog(const Utils::FilePath &exportPath,
AssetExporter &assetExporter, QWidget *parent) : AssetExporter &assetExporter, FilePathModel &model,
QWidget *parent) :
QDialog(parent), QDialog(parent),
m_assetExporter(assetExporter), m_assetExporter(assetExporter),
m_filePathModel(model),
m_ui(new Ui::AssetExportDialog), m_ui(new Ui::AssetExportDialog),
m_model(this) m_filesView(new QListView),
m_exportLogs(new QPlainTextEdit),
m_outputFormatter(new Utils::OutputFormatter())
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
m_ui->filesView->setModel(&m_model); m_ui->stackedWidget->addWidget(m_filesView);
m_filesView->setModel(&m_filePathModel);
m_exportLogs->setReadOnly(true);
m_outputFormatter->setPlainTextEdit(m_exportLogs);
m_ui->stackedWidget->addWidget(m_exportLogs);
switchView(false);
connect(m_ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this]() {
m_assetExporter.cancel();
});
m_exportBtn = m_ui->buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole); m_exportBtn = m_ui->buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
m_exportBtn->setEnabled(false); m_exportBtn->setEnabled(false);
connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport); connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport);
m_ui->exportPathEdit->setText(exportPath.toString()); connect(&m_filePathModel, &FilePathModel::modelReset, this, [this]() {
m_ui->exportProgress->setRange(0, 1000);
connect(&m_assetExporter, &AssetExporter::qmlFileResult, this, [this] (const Utils::FilePath &path) { m_ui->exportProgress->setValue(0);
m_qmlFiles.append(path); m_exportBtn->setEnabled(true);
QStringList files = m_model.stringList();
files.append(path.toString());
m_model.setStringList(files);
}); });
connect(m_ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this]() {
close();
});
m_ui->buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
m_ui->exportPathEdit->setFileName(exportPath);
m_ui->exportPathEdit->setPromptDialogTitle(tr("Choose Export Path"));
connect(&m_assetExporter, &AssetExporter::stateChanged, connect(&m_assetExporter, &AssetExporter::stateChanged,
this, &AssetExportDialog::onExportStateChanged); this, &AssetExportDialog::onExportStateChanged);
connect(&m_assetExporter, &AssetExporter::exportProgressChanged,
this, &AssetExportDialog::updateExportProgress);
connect(TaskHub::instance(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded);
m_ui->exportProgress->setRange(0,0);
} }
AssetExportDialog::~AssetExportDialog() AssetExportDialog::~AssetExportDialog()
@@ -70,25 +123,61 @@ AssetExportDialog::~AssetExportDialog()
void AssetExportDialog::onExport() void AssetExportDialog::onExport()
{ {
m_assetExporter.exportQml(m_qmlFiles, switchView(true);
Utils::FilePath::fromString(m_ui->exportPathEdit->text()));
updateExportProgress(0.0);
TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT);
m_exportLogs->clear();
m_assetExporter.exportQml(m_filePathModel.files(), m_ui->exportPathEdit->fileName());
} }
void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState) void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState)
{ {
switch (newState) { switch (newState) {
case AssetExporter::ParsingState::PreProcessing:
m_model.setStringList({});
break;
case AssetExporter::ParsingState::ExportingDone: case AssetExporter::ParsingState::ExportingDone:
m_exportBtn->setVisible(false);
m_ui->buttonBox->button(QDialogButtonBox::Close)->setVisible(true);
QMessageBox::information(this, tr("QML Export"), tr("Done")); QMessageBox::information(this, tr("QML Export"), tr("Done"));
break; break;
default: default:
break; break;
} }
m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::PreProcessingFinished || m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone);
newState == AssetExporter::ParsingState::ExportingDone);
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy()); m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy());
} }
void AssetExportDialog::updateExportProgress(double value)
{
value = std::max(0.0, std::min(1.0, value));
m_ui->exportProgress->setValue(std::round(value * 1000));
}
void AssetExportDialog::switchView(bool showExportView)
{
if (showExportView)
m_ui->stackedWidget->setCurrentWidget(m_exportLogs);
else
m_ui->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

@@ -34,13 +34,24 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QPushButton; class QPushButton;
class QListView;
class QPlainTextEdit;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Ui { namespace Ui {
class AssetExportDialog; class AssetExportDialog;
} }
namespace Utils {
class OutputFormatter;
}
namespace ProjectExplorer {
class Task;
}
namespace QmlDesigner { namespace QmlDesigner {
class FilePathModel;
class AssetExportDialog : public QDialog class AssetExportDialog : public QDialog
{ {
@@ -48,19 +59,24 @@ class AssetExportDialog : public QDialog
public: public:
explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter, explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter,
QWidget *parent = nullptr); FilePathModel& model, QWidget *parent = nullptr);
~AssetExportDialog(); ~AssetExportDialog();
private: private:
void onExport(); void onExport();
void onExportStateChanged(AssetExporter::ParsingState newState); void onExportStateChanged(AssetExporter::ParsingState newState);
void updateExportProgress(double value);
void switchView(bool showExportView);
void onTaskAdded(const ProjectExplorer::Task &task);
private: private:
AssetExporter &m_assetExporter; AssetExporter &m_assetExporter;
FilePathModel &m_filePathModel;
std::unique_ptr<Ui::AssetExportDialog> m_ui; std::unique_ptr<Ui::AssetExportDialog> m_ui;
QPushButton *m_exportBtn = nullptr; QPushButton *m_exportBtn = nullptr;
QStringListModel m_model; QListView *m_filesView = nullptr;
Utils::FilePaths m_qmlFiles; QPlainTextEdit *m_exportLogs = nullptr;
Utils::OutputFormatter *m_outputFormatter = nullptr;
}; };
} }

View File

@@ -14,21 +14,52 @@
<string>Export QML</string> <string>Export QML</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0"> <item row="0" column="1">
<widget class="QListView" name="filesView"/> <widget class="Utils::PathChooser" name="exportPathEdit" native="true"/>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLineEdit" name="exportPathEdit"/> <widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Export path:</string>
</property>
</widget>
</item> </item>
<item row="2" column="0"> <item row="1" column="0" colspan="2">
<widget class="QStackedWidget" name="stackedWidget"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QProgressBar" name="exportProgress">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>Utils::PathChooser</class>
<extends>QWidget</extends>
<header location="global">utils/pathchooser.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@@ -26,21 +26,11 @@
#include "componentexporter.h" #include "componentexporter.h"
#include "exportnotification.h" #include "exportnotification.h"
#include "plaintexteditmodifier.h"
#include "rewriterview.h"
#include "projectexplorer/project.h"
#include "projectexplorer/projectnodes.h"
#include "utils/runextensions.h"
#include "utils/qtcassert.h" #include "utils/qtcassert.h"
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMessageBox>
#include <QPlainTextEdit>
using namespace ProjectExplorer; using namespace ProjectExplorer;
@@ -48,48 +38,6 @@ namespace {
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg) Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg)
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg) Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg)
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg) Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg)
void findQmlFiles(QFutureInterface<Utils::FilePath> &f, const Project *project)
{
if (!project && !f.isCanceled())
f.reportFinished({});
int index = 0;
Utils::FilePaths qmlFiles = project->files([&f, &index](const Node* node) ->bool {
if (f.isCanceled())
return false;
Utils::FilePath path = node->filePath();
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
if (isComponent && node->filePath().endsWith(".ui.qml"))
f.reportResult(path, index++);
return true;
});
f.reportFinished();
}
//static QmlDesigner::Model* createModel(const Utils::FilePath &fileName)
//{
// QmlDesigner::Model *model = QmlDesigner::Model::create("Item", 2, 7);
// Utils::FileReader reader;
// QTC_ASSERT(reader.fetch(fileName.toString()), return nullptr);
// auto textEdit = new QPlainTextEdit;
// textEdit->setPlainText(QString::fromUtf8(reader.data()));
// auto modifier = new QmlDesigner::NotIndentingTextEditModifier(textEdit);
// modifier->setParent(model);
// auto rewriterView = new QmlDesigner::RewriterView(QmlDesigner::RewriterView::Validate, model);
// rewriterView->setCheckSemanticErrors(false);
// rewriterView->setTextModifier(modifier);
// model->attachView(rewriterView);
// QTC_ASSERT(rewriterView->rootModelNode().isValid(), return nullptr);
// return model;
//}
} }
namespace QmlDesigner { namespace QmlDesigner {
@@ -109,29 +57,6 @@ AssetExporter::~AssetExporter()
cancel(); cancel();
} }
bool AssetExporter::preProcessProject()
{
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
!m_preprocessWatcher->isFinished()) {
qCDebug(loggerInfo) << "Previous pre-processing not finished.";
return false;
}
m_currentState.change(ParsingState::PreProcessing);
m_preprocessWatcher.reset(new QFutureWatcher<Utils::FilePath>(this));
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::resultReadyAt, this,
[this](int index) {
emit qmlFileResult(m_preprocessWatcher->resultAt(index));
});
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished, this,
[this] () { m_currentState.change(ParsingState::PreProcessingFinished); });
QFuture<Utils::FilePath> f = Utils::runAsync(&findQmlFiles, m_project);
m_preprocessWatcher->setFuture(f);
return true;
}
void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
bool exportAssets) bool exportAssets)
{ {
@@ -139,6 +64,7 @@ void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::Fil
.arg(exportPath.toUserOutput()) .arg(exportPath.toUserOutput())
.arg(exportAssets? tr("Yes") : tr("No"))); .arg(exportAssets? tr("Yes") : tr("No")));
// TODO Asset export // TODO Asset export
notifyProgress(0.0);
Q_UNUSED(exportAssets); Q_UNUSED(exportAssets);
m_exportFiles = qmlFiles; m_exportFiles = qmlFiles;
m_components = QJsonArray(); m_components = QJsonArray();
@@ -149,18 +75,12 @@ void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::Fil
void AssetExporter::cancel() void AssetExporter::cancel()
{ {
ExportNotification::addInfo("Cancelling export."); // TODO Cancel export
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
!m_preprocessWatcher->isFinished()) {
m_preprocessWatcher->cancel();
m_preprocessWatcher->waitForFinished();
}
} }
bool AssetExporter::isBusy() const bool AssetExporter::isBusy() const
{ {
return m_currentState == AssetExporter::ParsingState::PreProcessing || return m_currentState == AssetExporter::ParsingState::Parsing ||
m_currentState == AssetExporter::ParsingState::Parsing ||
m_currentState == AssetExporter::ParsingState::ExportingAssets || m_currentState == AssetExporter::ParsingState::ExportingAssets ||
m_currentState == AssetExporter::ParsingState::WritingJson; m_currentState == AssetExporter::ParsingState::WritingJson;
} }
@@ -190,6 +110,11 @@ void AssetExporter::notifyLoadError(AssetExporterView::LoadState state)
ExportNotification::addError(tr("Loading QML failed. %1").arg(errorStr)); ExportNotification::addError(tr("Loading QML failed. %1").arg(errorStr));
} }
void AssetExporter::notifyProgress(double value) const
{
emit exportProgressChanged(value);
}
void AssetExporter::onQmlFileLoaded() void AssetExporter::onQmlFileLoaded()
{ {
QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return); QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return);
@@ -206,6 +131,7 @@ void AssetExporter::triggerLoadNextFile()
void AssetExporter::loadNextFile() void AssetExporter::loadNextFile()
{ {
if (m_exportFiles.isEmpty()) { if (m_exportFiles.isEmpty()) {
notifyProgress(0.8);
m_currentState.change(ParsingState::ParsingFinished); m_currentState.change(ParsingState::ParsingFinished);
writeMetadata(); writeMetadata();
return; return;
@@ -220,8 +146,9 @@ void AssetExporter::loadNextFile()
void AssetExporter::writeMetadata() const void AssetExporter::writeMetadata() const
{ {
Utils::FilePath metadataPath = m_exportPath.pathAppended(m_exportPath.fileName() + ".metadata");
ExportNotification::addInfo(tr("Writing metadata to file %1."). ExportNotification::addInfo(tr("Writing metadata to file %1.").
arg(m_exportPath.toUserOutput())); arg(metadataPath.toUserOutput()));
m_currentState.change(ParsingState::WritingJson); m_currentState.change(ParsingState::WritingJson);
QJsonObject jsonRoot; // TODO: Write plugin info to root QJsonObject jsonRoot; // TODO: Write plugin info to root
jsonRoot.insert("artboards", m_components); jsonRoot.insert("artboards", m_components);
@@ -229,13 +156,14 @@ void AssetExporter::writeMetadata() const
if (doc.isNull() || doc.isEmpty()) { if (doc.isNull() || doc.isEmpty()) {
ExportNotification::addError(tr("Empty JSON document.")); ExportNotification::addError(tr("Empty JSON document."));
} else { } else {
Utils::FileSaver saver(m_exportPath.toString(), QIODevice::Text); Utils::FileSaver saver(metadataPath.toString(), QIODevice::Text);
saver.write(doc.toJson(QJsonDocument::Indented)); saver.write(doc.toJson(QJsonDocument::Indented));
if (!saver.finalize()) { if (!saver.finalize()) {
ExportNotification::addError(tr("Writing metadata failed. %1"). ExportNotification::addError(tr("Writing metadata failed. %1").
arg(saver.errorString())); arg(saver.errorString()));
} }
} }
notifyProgress(1.0);
m_currentState.change(ParsingState::ExportingDone); m_currentState.change(ParsingState::ExportingDone);
} }

View File

@@ -27,7 +27,6 @@
#include "assetexporterview.h" #include "assetexporterview.h"
#include "utils/fileutils.h" #include "utils/fileutils.h"
#include <QFutureWatcher>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
@@ -51,8 +50,6 @@ public:
enum class ParsingState { enum class ParsingState {
Idle = 0, Idle = 0,
PreProcessing,
PreProcessingFinished,
Parsing, Parsing,
ParsingFinished, ParsingFinished,
ExportingAssets, ExportingAssets,
@@ -65,21 +62,22 @@ public:
QObject *parent = nullptr); QObject *parent = nullptr);
~AssetExporter(); ~AssetExporter();
bool preProcessProject(); void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, bool exportAssets = false); bool exportAssets = false);
void cancel(); void cancel();
bool isBusy() const; bool isBusy() const;
signals: signals:
void qmlFileResult(Utils::FilePath);
void stateChanged(ParsingState); void stateChanged(ParsingState);
void exportProgressChanged(double) const;
private: private:
ParsingState currentState() const { return m_currentState.m_state; } ParsingState currentState() const { return m_currentState.m_state; }
void exportComponent(const ModelNode &rootNode); void exportComponent(const ModelNode &rootNode);
void writeMetadata() const; void writeMetadata() const;
void notifyLoadError(AssetExporterView::LoadState state); void notifyLoadError(AssetExporterView::LoadState state);
void notifyProgress(double value) const;
void triggerLoadNextFile(); void triggerLoadNextFile();
void loadNextFile(); void loadNextFile();
@@ -98,7 +96,6 @@ private:
Utils::FilePaths m_exportFiles; Utils::FilePaths m_exportFiles;
Utils::FilePath m_exportPath; Utils::FilePath m_exportPath;
QJsonArray m_components; QJsonArray m_components;
std::unique_ptr<QFutureWatcher<Utils::FilePath>> m_preprocessWatcher;
}; };
QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s); QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s);

View File

@@ -29,6 +29,7 @@
#include "assetexportdialog.h" #include "assetexportdialog.h"
#include "assetexporter.h" #include "assetexporter.h"
#include "assetexporterview.h" #include "assetexporterview.h"
#include "filepathmodel.h"
#include "componentexporter.h" #include "componentexporter.h"
#include "parsers/modelitemnodeparser.h" #include "parsers/modelitemnodeparser.h"
@@ -89,14 +90,10 @@ void AssetExporterPlugin::onExport()
if (!startupProject) if (!startupProject)
return; return;
FilePathModel model(startupProject);
auto exportDir = startupProject->projectFilePath().parentDir(); auto exportDir = startupProject->projectFilePath().parentDir();
if (!exportDir.toFileInfo().isRoot())
exportDir = exportDir.parentDir();
auto defaultMetadataPath = exportDir.pathAppended(startupProject->displayName() + ".metadata");
AssetExporter assetExporter(m_view, startupProject); AssetExporter assetExporter(m_view, startupProject);
AssetExportDialog assetExporterDialog(defaultMetadataPath, assetExporter); AssetExportDialog assetExporterDialog(exportDir, assetExporter, model);
assetExporter.preProcessProject();
assetExporterDialog.exec(); assetExporterDialog.exec();
} }

View File

@@ -14,6 +14,7 @@ HEADERS += \
assetexportpluginconstants.h \ assetexportpluginconstants.h \
componentexporter.h \ componentexporter.h \
exportnotification.h \ exportnotification.h \
filepathmodel.h \
parsers/modelitemnodeparser.h \ parsers/modelitemnodeparser.h \
parsers/modelnodeparser.h parsers/modelnodeparser.h
@@ -24,6 +25,7 @@ SOURCES += \
assetexporterview.cpp \ assetexporterview.cpp \
componentexporter.cpp \ componentexporter.cpp \
exportnotification.cpp \ exportnotification.cpp \
filepathmodel.cpp \
parsers/modelitemnodeparser.cpp \ parsers/modelitemnodeparser.cpp \
parsers/modelnodeparser.cpp parsers/modelnodeparser.cpp

View File

@@ -45,6 +45,8 @@ QtcProduct {
"componentexporter.h", "componentexporter.h",
"exportnotification.cpp", "exportnotification.cpp",
"exportnotification.h", "exportnotification.h",
"filepathmodel.cpp",
"filepathmodel.h",
"parsers/modelitemnodeparser.cpp", "parsers/modelitemnodeparser.cpp",
"parsers/modelitemnodeparser.h", "parsers/modelitemnodeparser.h",
"parsers/modelnodeparser.cpp", "parsers/modelnodeparser.cpp",

View File

@@ -0,0 +1,163 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "filepathmodel.h"
#include "exportnotification.h"
#include "projectexplorer/project.h"
#include "projectexplorer/projectnodes.h"
#include "utils/runextensions.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(QFutureInterface<Utils::FilePath> &f, const Project *project)
{
if (!project && !f.isCanceled())
f.reportFinished({});
int index = 0;
Utils::FilePaths qmlFiles = project->files([&f, &index](const Node* node) ->bool {
if (f.isCanceled())
return false;
Utils::FilePath path = node->filePath();
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
if (isComponent && node->filePath().endsWith(".ui.qml"))
f.reportResult(path, index++);
return true;
});
f.reportFinished();
}
}
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 QML files preparation."));
m_preprocessWatcher->cancel();
m_preprocessWatcher->waitForFinished();
qCDebug(loggerInfo) << "Canceling QML files preparation done.";
}
}
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.count();
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.count(), m_files.count());
m_files.append(m_preprocessWatcher->resultAt(resultIndex));
endInsertRows();
});
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
this, &FilePathModel::endResetModel);
QFuture<Utils::FilePath> f = Utils::runAsync(&findQmlFiles, m_project);
m_preprocessWatcher->setFuture(f);
}
}

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QAbstractListModel>
#include <QFutureWatcher>
#include "utils/fileutils.h"
#include <memory>
#include <unordered_set>
namespace ProjectExplorer {
class Project;
}
namespace QmlDesigner {
class FilePathModel : public QAbstractListModel
{
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;
};
}