Files
qt-creator/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp
Burak Hancerli ccf17b02c8 QmlDesigner: Fix project file getter/setter issues
Task-number: QDS-9781
Change-Id: I2df8cca1c4e5cd10563621b849fb54b29c0d155d
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
2023-04-26 12:55:59 +00:00

369 lines
16 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "converters.h"
#include <QJsonArray>
namespace QmlProjectManager::Converters {
using PropsPair = QPair<QString, QStringList>;
struct FileProps
{
const PropsPair image{"image", QStringList{"*.jpeg", "*.jpg", "*.png", "*.svg", "*.hdr", ".ktx"}};
const PropsPair qml{"qml", QStringList{"*.qml"}};
const PropsPair qmlDir{"qmldir", QStringList{"qmldir"}};
const PropsPair javaScr{"javaScript", QStringList{"*.js", "*.ts"}};
const PropsPair video{"video", QStringList{"*.mp4"}};
const PropsPair sound{"sound", QStringList{"*.mp3", "*.wav"}};
const PropsPair font{"font", QStringList{"*.ttf", "*.otf"}};
const PropsPair config{"config", QStringList{"*.conf"}};
const PropsPair styling{"styling", QStringList{"*.css"}};
const PropsPair mesh{"meshes", QStringList{"*.mesh"}};
const PropsPair
shader{"shader",
QStringList{"*.glsl", "*.glslv", "*.glslf", "*.vsh", "*.fsh", "*.vert", "*.frag"}};
};
QString jsonToQmlProject(const QJsonObject &rootObject)
{
QString qmlProjectString;
QTextStream ts{&qmlProjectString};
QJsonObject runConfig = rootObject["runConfig"].toObject();
QJsonObject languageConfig = rootObject["language"].toObject();
QJsonObject shaderConfig = rootObject["shaderTool"].toObject();
QJsonObject versionConfig = rootObject["versions"].toObject();
QJsonObject environmentConfig = rootObject["environment"].toObject();
QJsonObject deploymentConfig = rootObject["deployment"].toObject();
QJsonObject filesConfig = rootObject["fileGroups"].toObject();
int indentationLevel = 0;
auto appendBreak = [&ts]() { ts << Qt::endl; };
auto appendComment = [&ts, &indentationLevel](const QString &comment) {
ts << QString(" ").repeated(indentationLevel * 4) << "\\\\ " << comment << Qt::endl;
};
auto appendItem =
[&ts, &indentationLevel](const QString &key, const QString &value, const bool isEnclosed) {
ts << QString(" ").repeated(indentationLevel * 4) << key << ": "
<< (isEnclosed ? "\"" : "") << value << (isEnclosed ? "\"" : "") << Qt::endl;
};
auto appendString = [&appendItem](const QString &key, const QString &val) {
appendItem(key, val, true);
};
auto appendBool = [&appendItem](const QString &key, const bool &val) {
appendItem(key, QString::fromStdString(val ? "true" : "false"), false);
};
auto appendArray = [&appendItem](const QString &key, const QStringList &vals) {
QString finalString;
foreach (const QString &value, vals) {
finalString.append("\"").append(value).append("\"").append(",");
}
finalString.remove(finalString.length() - 1, 1);
finalString.prepend("[ ").append(" ]");
appendItem(key, finalString, false);
};
auto startObject = [&ts, &indentationLevel](const QString &objectName) {
ts << Qt::endl
<< QString(" ").repeated(indentationLevel * 4) << objectName << " {" << Qt::endl;
indentationLevel++;
};
auto endObject = [&ts, &indentationLevel]() {
indentationLevel--;
ts << QString(" ").repeated(indentationLevel * 4) << "}" << Qt::endl;
};
auto appendDirectories =
[&startObject, &endObject, &appendString, &filesConfig](const QString &jsonKey,
const QString &qmlKey) {
QJsonValue dirsObj = filesConfig[jsonKey].toObject()["directories"];
QStringList dirs = dirsObj.toVariant().toStringList();
foreach (const QString &directory, dirs) {
startObject(qmlKey);
appendString("directory", directory);
endObject();
}
};
auto appendFiles = [&startObject,
&endObject,
&appendString,
&appendArray,
&filesConfig](const QString &jsonKey, const QString &qmlKey) {
QJsonValue dirsObj = filesConfig[jsonKey].toObject()["directories"];
QJsonValue filesObj = filesConfig[jsonKey].toObject()["files"];
QJsonValue filtersObj = filesConfig[jsonKey].toObject()["filters"];
foreach (const QString &directory, dirsObj.toVariant().toStringList()) {
startObject(qmlKey);
appendString("directory", directory);
appendString("filters", filtersObj.toVariant().toStringList().join(";"));
if (!filesObj.toArray().isEmpty()) {
QStringList fileList;
foreach (const QJsonValue &file, filesObj.toArray()) {
fileList.append(file.toObject()["name"].toString());
}
appendArray("files", fileList);
}
endObject();
}
};
// start creating the file content
appendComment("prop: json-converted");
appendComment("prop: auto-generated");
ts << Qt::endl << "import QmlProject" << Qt::endl;
{
startObject("Project");
{ // append non-object props
appendString("mainFile", runConfig["mainFile"].toString());
appendString("mainUiFile", runConfig["mainUiFile"].toString());
appendString("targetDirectory", deploymentConfig["targetDirectory"].toString());
appendBool("widgetApp", runConfig["widgetApp"].toBool());
appendArray("importPaths", rootObject["importPaths"].toVariant().toStringList());
appendBreak();
appendString("qdsVersion", versionConfig["designStudio"].toString());
appendString("quickVersion", versionConfig["qtQuick"].toString());
appendBool("qt6Project", versionConfig["qt"].toString() == "6");
appendBool("qtForMCUs", rootObject["mcuConfig"].toObject().isEmpty());
appendBreak();
appendBool("multilanguageSupport", languageConfig["multiLanguageSupport"].toBool());
appendString("primaryLanguage", languageConfig["primaryLanguage"].toString());
appendArray("supportedLanguages",
languageConfig["supportedLanguages"].toVariant().toStringList());
}
{ // append Environment object
startObject("Environment");
foreach (const QString &key, environmentConfig.keys()) {
appendItem(key, environmentConfig[key].toString(), true);
}
endObject();
}
{ // append ShaderTool object
startObject("ShaderTool");
appendString("args", shaderConfig["args"].toVariant().toStringList().join(" "));
appendArray("files", shaderConfig["files"].toVariant().toStringList());
endObject();
}
{ // append files objects
appendDirectories("qml", "QmlFiles");
appendDirectories("javaScript", "JavaScriptFiles");
appendDirectories("image", "ImageFiles");
appendFiles("config", "Files");
appendFiles("font", "Files");
appendFiles("meshes", "Files");
appendFiles("qmldir", "Files");
appendFiles("shader", "Files");
appendFiles("sound", "Files");
appendFiles("video", "Files");
}
endObject(); // Closing 'Project'
}
return qmlProjectString;
}
QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile)
{
QmlJS::SimpleReader simpleQmlJSReader;
const QmlJS::SimpleReaderNode::Ptr rootNode = simpleQmlJSReader.readFile(projectFile.toString());
if (!simpleQmlJSReader.errors().isEmpty() || !rootNode->isValid()) {
qCritical() << "Unable to parse:" << projectFile;
qCritical() << simpleQmlJSReader.errors();
return {};
}
if (rootNode->name() != QLatin1String("Project")) {
qCritical() << "Cannot find root 'Proejct' item in the project file: " << projectFile;
return {};
}
auto nodeToJsonObject = [](const QmlJS::SimpleReaderNode::Ptr &node) {
QJsonObject tObj;
foreach (const QString &childPropName, node->propertyNames()) {
tObj.insert(childPropName, node->property(childPropName).value.toJsonValue());
}
return tObj;
};
auto toCamelCase = [](const QString &s) { return QString(s).replace(0, 1, s[0].toLower()); };
QJsonObject rootObject; // root object
QJsonObject fileGroupsObject;
QJsonObject languageObject;
QJsonObject versionObject;
QJsonObject runConfigObject;
QJsonObject deploymentObject;
QJsonObject mcuObject;
QJsonObject shaderToolObject;
// convert the the non-object props
for (const QString &propName : rootNode->propertyNames()) {
QJsonObject *currentObj = &rootObject;
QString objKey = propName;
QJsonValue value = rootNode->property(propName).value.toJsonValue();
if (propName.contains("language", Qt::CaseInsensitive)) {
currentObj = &languageObject;
if (propName.toLower() == "multilanguagesupport") // fixing the camelcase
objKey = "multiLanguageSupport";
} else if (propName.contains("version", Qt::CaseInsensitive)) {
currentObj = &versionObject;
if (propName.toLower() == "qdsversion")
objKey = "designStudio";
else if (propName.toLower() == "quickversion")
objKey = "qtQuick";
} else if (propName.contains("widgetapp", Qt::CaseInsensitive)
|| propName.contains("fileselector", Qt::CaseInsensitive)
|| propName.contains("mainfile", Qt::CaseInsensitive)
|| propName.contains("mainuifile", Qt::CaseInsensitive)
|| propName.contains("forcefreetype", Qt::CaseInsensitive)) {
currentObj = &runConfigObject;
} else if (propName.contains("targetdirectory", Qt::CaseInsensitive)) {
currentObj = &deploymentObject;
} else if (propName.contains("qtformcus", Qt::CaseInsensitive)) {
currentObj = &mcuObject;
objKey = "mcuEnabled";
} else if (propName.contains("qt6project", Qt::CaseInsensitive)) {
currentObj = &versionObject;
objKey = "qt";
value = rootNode->property(propName).value.toBool() ? "6" : "5";
}
currentObj->insert(objKey, value);
}
// add missing non-object props if any
if (!runConfigObject.contains("fileSelectors")) {
runConfigObject.insert("fileSelectors", QJsonArray{});
}
if (!versionObject.contains("qt")) {
versionObject.insert("qt", "5");
}
// convert the the object props
for (const QmlJS::SimpleReaderNode::Ptr &childNode : rootNode->children()) {
if (childNode->name().contains("files", Qt::CaseInsensitive)) {
PropsPair propsPair;
FileProps fileProps;
const QString childNodeName = childNode->name().toLower();
const QmlJS::SimpleReaderNode::Property childNodeFilter = childNode->property("filter");
const QmlJS::SimpleReaderNode::Property childNodeDirectory = childNode->property("directory");
const QmlJS::SimpleReaderNode::Property childNodeFiles = childNode->property("files");
const QString childNodeFilterValue = childNodeFilter.value.toString();
if (childNodeName == "qmlfiles" || childNodeFilterValue.contains("*.qml")) {
propsPair = fileProps.qml;
} else if (childNodeName == "javascriptfiles") {
propsPair = fileProps.javaScr;
} else if (childNodeName == "imagefiles") {
propsPair = fileProps.image;
} else {
if (childNodeFilter.isValid()) {
if (childNodeFilterValue.contains(".conf"))
propsPair = fileProps.config;
else if (childNodeFilterValue.contains(".ttf"))
propsPair = fileProps.font;
else if (childNodeFilterValue.contains("qmldir"))
propsPair = fileProps.qmlDir;
else if (childNodeFilterValue.contains(".wav"))
propsPair = fileProps.sound;
else if (childNodeFilterValue.contains(".mp4"))
propsPair = fileProps.video;
else if (childNodeFilterValue.contains(".mesh"))
propsPair = fileProps.mesh;
else if (childNodeFilterValue.contains(".glsl"))
propsPair = fileProps.shader;
else if (childNodeFilterValue.contains(".css"))
propsPair = fileProps.styling;
}
}
// get all objects we'll work on
QJsonObject targetObject = fileGroupsObject[propsPair.first].toObject();
QJsonArray directories = targetObject["directories"].toArray();
QJsonArray filters = targetObject["filters"].toArray();
QJsonArray files = targetObject["files"].toArray();
// populate & update filters
if (filters.isEmpty()) {
filters = QJsonArray::fromStringList(propsPair.second); // populate the filters with the predefined ones
}
if (childNodeFilter.isValid()) { // append filters from qmlproject (merge)
const QStringList filtersFromProjectFile = childNodeFilterValue.split(
";");
for (const QString &filter : filtersFromProjectFile) {
if (!filters.contains(QJsonValue(filter))) {
filters.append(QJsonValue(filter));
}
}
}
// populate & update directories
if (childNodeDirectory.isValid()) {
directories.append(childNodeDirectory.value.toJsonValue());
}
if (directories.isEmpty())
directories.append(".");
// populate & update files
if (childNodeFiles.isValid()) {
foreach (const QJsonValue &file, childNodeFiles.value.toJsonArray()) {
files.append(QJsonObject{{"name", file.toString()}});
}
}
// put everything back into the root object
targetObject.insert("directories", directories);
targetObject.insert("filters", filters);
targetObject.insert("files", files);
fileGroupsObject.insert(propsPair.first, targetObject);
} else if (childNode->name().contains("shadertool", Qt::CaseInsensitive)) {
QStringList quotedArgs = childNode->property("args").value.toString().split('\"');
QStringList args;
for (int i = 0; i < quotedArgs.size(); ++i) {
// Each odd arg in this list is a single quoted argument, which we should
// not be split further
if (i % 2 == 0)
args.append(quotedArgs[i].trimmed().split(' '));
else
args.append(quotedArgs[i].prepend("\"").append("\""));
}
shaderToolObject.insert("args", QJsonArray::fromStringList(args));
shaderToolObject.insert("files", childNode->property("files").value.toJsonValue());
} else {
rootObject.insert(toCamelCase(childNode->name()), nodeToJsonObject(childNode));
}
}
rootObject.insert("fileGroups", fileGroupsObject);
rootObject.insert("language", languageObject);
rootObject.insert("versions", versionObject);
rootObject.insert("runConfig", runConfigObject);
rootObject.insert("deployment", deploymentObject);
rootObject.insert("mcuConfig", mcuObject);
rootObject.insert("shaderTool", shaderToolObject);
rootObject.insert("fileVersion", 1);
return rootObject;
}
} // namespace QmlProjectManager::Converters