QmlProjectManager: Add .qmlproject file generator

Task-number: QDS-6993
Change-Id: I033b0efeea87e8eab3238d3ce8d5f25752822e36
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Tapani Mattila
2022-05-25 14:15:17 +03:00
parent dec3fb7503
commit 82911d2ec3
9 changed files with 455 additions and 13 deletions

View File

@@ -15,6 +15,8 @@ add_qtc_plugin(QmlProjectManager
cmakegen/generatecmakelists.cpp cmakegen/generatecmakelists.h
cmakegen/generatecmakelistsconstants.h
cmakegen/boilerplate.qrc
qmlprojectgen/qmlprojectgenerator.cpp qmlprojectgen/qmlprojectgenerator.h
qmlprojectgen/templates.qrc
projectfilecontenttools.cpp projectfilecontenttools.h
qdslandingpage.cpp qdslandingpage.h
qdslandingpagetheme.cpp qdslandingpagetheme.h

View File

@@ -38,6 +38,12 @@ namespace Internal {
const char QMLRESOURCEPATH[] = "qmldesigner/propertyEditorQmlSources/imports";
const char LANDINGPAGEPATH[] = "qmldesigner/landingpage";
const char PROPERTY_QDSINSTALLED[] = "qdsInstalled";
const char PROPERTY_PROJECTFILEEXISTS[] = "projectFileExists";
const char PROPERTY_QTVERSION[] = "qtVersion";
const char PROPERTY_QDSVERSION[] = "qdsVersion";
const char PROPERTY_CMAKES[] = "cmakeLists";
const char PROPERTY_REMEMBER[] = "rememberSelection";
QdsLandingPageWidget::QdsLandingPageWidget(QWidget* parent)
: QWidget(parent)
@@ -97,12 +103,12 @@ QWidget *QdsLandingPage::widget()
void QdsLandingPage::show()
{
m_widget->rootObject()->setProperty("qdsInstalled", m_qdsInstalled);
m_widget->rootObject()->setProperty("projectFileExists", m_projectFileExists);
m_widget->rootObject()->setProperty("qtVersion", m_qtVersion);
m_widget->rootObject()->setProperty("qdsVersion", m_qdsVersion);
m_widget->rootObject()->setProperty("cmakeLists", m_cmakeResources);
m_widget->rootObject()->setProperty("rememberSelection", Qt::Unchecked);
m_widget->rootObject()->setProperty(PROPERTY_QDSINSTALLED, m_qdsInstalled);
m_widget->rootObject()->setProperty(PROPERTY_PROJECTFILEEXISTS, m_projectFileExists);
m_widget->rootObject()->setProperty(PROPERTY_QTVERSION, m_qtVersion);
m_widget->rootObject()->setProperty(PROPERTY_QDSVERSION, m_qdsVersion);
m_widget->rootObject()->setProperty(PROPERTY_CMAKES, m_cmakeResources);
m_widget->rootObject()->setProperty(PROPERTY_REMEMBER, Qt::Unchecked);
m_widget->show();
}
@@ -119,6 +125,8 @@ bool QdsLandingPage::qdsInstalled() const
void QdsLandingPage::setQdsInstalled(bool installed)
{
m_qdsInstalled = installed;
if (m_widget->rootObject())
m_widget->rootObject()->setProperty(PROPERTY_QDSINSTALLED, installed);
}
bool QdsLandingPage::projectFileExists() const
@@ -129,6 +137,8 @@ bool QdsLandingPage::projectFileExists() const
void QdsLandingPage::setProjectFileExists(bool exists)
{
m_projectFileExists = exists;
if (m_widget->rootObject())
m_widget->rootObject()->setProperty(PROPERTY_PROJECTFILEEXISTS, exists);
}
const QString QdsLandingPage::qtVersion() const
@@ -139,6 +149,8 @@ const QString QdsLandingPage::qtVersion() const
void QdsLandingPage::setQtVersion(const QString &version)
{
m_qtVersion = version;
if (m_widget->rootObject())
m_widget->rootObject()->setProperty(PROPERTY_QTVERSION, version);
}
const QString QdsLandingPage::qdsVersion() const
@@ -149,6 +161,8 @@ const QString QdsLandingPage::qdsVersion() const
void QdsLandingPage::setQdsVersion(const QString &version)
{
m_qdsVersion = version;
if (m_widget->rootObject())
m_widget->rootObject()->setProperty(PROPERTY_QDSVERSION, version);
}
const QStringList QdsLandingPage::cmakeResources() const

View File

@@ -0,0 +1,72 @@
import QmlProject
Project {
/* Include .qml, .js, and image files from current directory and subdirectories */
%1
%2
%3
%4
Files {
filter: "*.conf"
files: ["qtquickcontrols2.conf"]
}
Files {
filter: "qmldir"
directory: "."
}
Files {
filter: "*.ttf;*.otf"
}
Files {
filter: "*.wav;*.mp3"
}
Files {
filter: "*.mp4"
}
Files {
filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag"
}
Environment {
QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
QT_AUTO_SCREEN_SCALE_FACTOR: "1"
QT_LOGGING_RULES: "qt.qml.connections=false"
QT_ENABLE_HIGHDPI_SCALING: "0"
/* Useful for debugging
QSG_VISUALIZE=batches
QSG_VISUALIZE=clip
QSG_VISUALIZE=changes
QSG_VISUALIZE=overdraw
*/
}
qt6Project: true
createdInQtCreator: true
qdsVersion: "3.4"
%5
/* If any modules the project imports require widgets (e.g. QtCharts), widgetApp must be true */
widgetApp: true
/* args: Specifies command line arguments for qsb tool to generate shaders.
files: Specifies target files for qsb tool. If path is included, it must be relative to this file.
Wildcard '*' can be used in the file name part of the path.
e.g. files: [ "content/shaders/*.vert", "*.frag" ] */
ShaderTool {
args: "-s --glsl \\\"100 es,120,150\\\" --hlsl 50 --msl 12"
files: [ "content/shaders/*" ]
}
multilanguageSupport: true
supportedLanguages: ["en"]
primaryLanguage: "en"
}

View File

@@ -0,0 +1,265 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Design Tooling
**
** 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 "qmlprojectgenerator.h"
#include "cmakegen/generatecmakelists.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/icore.h>
#include <QMessageBox>
using namespace Utils;
namespace QmlProjectManager {
namespace GenerateQmlProject {
bool QmlProjectFileGenerator::prepareForUiQmlFile(const FilePath &uiQmlFilePath)
{
return prepare(selectTargetFile(uiQmlFilePath));
}
bool QmlProjectFileGenerator::prepare(const FilePath &targetDir)
{
m_targetFile = targetDir.isEmpty() ? selectTargetFile() : targetDir;
m_targetDir = m_targetFile.parentDir();
return true;
}
const char QMLPROJECT_FILE_TEMPLATE_PATH[] = ":/projectfiletemplates/qmlproject.tpl";
const char FILE_QML[] = "*.qml";
const char FILE_JS[] = "*.js";
const char FILE_JPG[] = "*.jpg";
const char FILE_PNG[] = "*.png";
const char FILE_GIF[] = "*.gif";
const char FILE_MESH[] = "*.mesh";
const char BLOCKTITLE_QML[] = "QmlFiles";
const char BLOCKTITLE_IMAGE[] = "ImageFiles";
const char BLOCKTITLE_JS[] = "JavaScriptFiles";
const char BLOCK_CONTENTDIR[] = "\n %1 {\n directory: \"%2\"\n }\n";
const char BLOCK_FILTEREDDIR[] = "\n Files {\n filter: \"%1\"\n directory: \"%2\"\n }\n";
const char BLOCK_DIRARRAY[] = "\n %1: [ %2 ]\n";
bool QmlProjectFileGenerator::execute()
{
static const QStringList filesQml = {FILE_QML};
static const QStringList filesImage = {FILE_PNG, FILE_JPG, FILE_GIF};
static const QStringList filesJs = {FILE_QML, FILE_JS};
static const QStringList filesAsset = {FILE_MESH};
if (m_targetFile.isEmpty() || !m_targetDir.isWritableDir())
return false;
const QString contentEntry = createContentDirEntries(BLOCKTITLE_QML, filesQml);
const QString imageEntry = createContentDirEntries(BLOCKTITLE_IMAGE, filesImage);
const QString jsEntry = createContentDirEntries(BLOCKTITLE_JS, filesJs);
const QString assetEntry = createFilteredDirEntries(filesAsset);
QStringList importDirs = findContentDirs(filesQml + filesAsset);
importDirs.removeAll(".");
importDirs.removeAll("content");
const QString importPaths = createDirArrayEntry("importPaths", importDirs);
const QString fileContent = GenerateCmake::readTemplate(QMLPROJECT_FILE_TEMPLATE_PATH)
.arg(contentEntry, imageEntry, jsEntry, assetEntry, importPaths);
QFile file(m_targetFile.toString());
file.open(QIODevice::WriteOnly);
if (!file.isOpen())
return false;
file.reset();
file.write(fileContent.toUtf8());
file.close();
QMessageBox::information(Core::ICore::dialogParent(),
QObject::tr("Project File Generated"),
QObject::tr("File created:\n\n%1").arg(m_targetFile.toString()),
QMessageBox::Ok);
return true;
}
const FilePath QmlProjectFileGenerator::targetDir() const
{
return m_targetDir;
}
const FilePath QmlProjectFileGenerator::targetFile() const
{
return m_targetFile;
}
bool QmlProjectFileGenerator::isStandardStructure(const FilePath &projectDir) const
{
if (projectDir.pathAppended("content").isDir() &&
projectDir.pathAppended("imports").isDir())
return true;
return false;
}
const QString QmlProjectFileGenerator::createContentDirEntries(const QString &containerName,
const QStringList &suffixes) const
{
QString entries;
const QStringList contentDirs = findContentDirs(suffixes);
for (const QString &dir : contentDirs)
entries.append(QString(BLOCK_CONTENTDIR).arg(containerName, dir));
return entries;
}
const QString QmlProjectFileGenerator::createFilteredDirEntries(const QStringList &suffixes) const
{
QString entries;
const QString filterList = suffixes.join(';');
const QStringList contentDirs = findContentDirs(suffixes);
for (const QString &dir : contentDirs)
entries.append(QString(BLOCK_FILTEREDDIR).arg(filterList, dir));
return entries;
}
const QString QmlProjectFileGenerator::createDirArrayEntry(const QString &arrayName,
const QStringList &relativePaths) const
{
if (relativePaths.isEmpty())
return {};
QStringList formattedDirs = relativePaths;
for (QString &dir : formattedDirs)
dir.append('"').prepend('"');
return QString(BLOCK_DIRARRAY).arg(arrayName, formattedDirs.join(", "));
}
const FilePath QmlProjectFileGenerator::selectTargetFile(const FilePath &uiFilePath)
{
FilePath suggestedDir;
if (!uiFilePath.isEmpty())
if (uiFilePath.parentDir().parentDir().exists())
suggestedDir = uiFilePath.parentDir().parentDir();
if (suggestedDir.isEmpty())
suggestedDir = FilePath::fromString(QDir::homePath());
FilePath targetFile;
bool selectionCompleted = false;
do {
targetFile = Core::DocumentManager::getSaveFileNameWithExtension(
QObject::tr("Select File Location"),
suggestedDir,
QObject::tr("Qt Design Studio Project Files (*.qmlproject)"));
selectionCompleted = isDirAcceptable(targetFile.parentDir(), uiFilePath);
} while (!selectionCompleted);
return targetFile;
}
bool QmlProjectFileGenerator::isDirAcceptable(const FilePath &dir, const FilePath &uiFile)
{
const FilePath uiFileParentDir = uiFile.parentDir();
if (dir.isChildOf(uiFileParentDir)) {
QMessageBox::warning(Core::ICore::dialogParent(),
QObject::tr("Invalid Directory"),
QObject::tr("Project file must be placed in a parent directory of the QML files."),
QMessageBox::Ok);
return false;
}
if (uiFileParentDir.isChildOf(dir)) {
const FilePath relativePath = uiFileParentDir.relativeChildPath(dir);
QStringList components = relativePath.toString().split("/");
if (components.size() > 2) {
QMessageBox::StandardButton sel = QMessageBox::question(Core::ICore::dialogParent(),
QObject::tr("Problem"),
QObject::tr("Selected directory is far away from the QML file. This can cause unexpected results.\n\nAre you sure?"),
QMessageBox::Yes | QMessageBox::No);
if (sel == QMessageBox::No)
return false;
}
}
return true;
}
const int TOO_FAR_AWAY = 4;
const QDir::Filters FILES_ONLY = QDir::Files;
const QDir::Filters DIRS_ONLY = QDir::Dirs|QDir::Readable|QDir::NoDotAndDotDot;
const FilePath QmlProjectFileGenerator::findInDirTree(const FilePath &dir, const QStringList &suffixes, int currentSearchDepth) const
{
if (currentSearchDepth > TOO_FAR_AWAY)
return {};
currentSearchDepth++;
const FilePaths files = dir.dirEntries({suffixes, FILES_ONLY});
if (!files.isEmpty())
return dir;
FilePaths subdirs = dir.dirEntries(DIRS_ONLY);
for (const FilePath &subdir : subdirs) {
const FilePath result = findInDirTree(subdir, suffixes, currentSearchDepth);
if (!result.isEmpty())
return result;
}
return {};
}
const QStringList QmlProjectFileGenerator::findContentDirs(const QStringList &suffixes) const
{
FilePaths results;
if (!isStandardStructure(m_targetDir))
if (!m_targetDir.dirEntries({suffixes, FILES_ONLY}).isEmpty())
return {"."};
const FilePaths dirs = m_targetDir.dirEntries(DIRS_ONLY);
for (const FilePath &dir : dirs) {
const FilePath result = findInDirTree(dir, suffixes);
if (!result.isEmpty())
results.append(result);
}
QStringList relativePaths;
for (const FilePath &fullPath : results) {
if (fullPath == m_targetDir)
relativePaths.append(".");
else
relativePaths.append(fullPath.relativeChildPath(m_targetDir).toString().split('/').first());
}
return relativePaths;
}
} // GenerateQmlProject
} // QmlProjectManager

View File

@@ -0,0 +1,61 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Design Tooling
**
** 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 <utils/filepath.h>
namespace QmlProjectManager {
namespace GenerateQmlProject {
class QmlProjectFileGenerator {
public:
bool prepare(const Utils::FilePath &targetDir);
bool prepareForUiQmlFile(const Utils::FilePath &uiFilePath);
bool execute();
const Utils::FilePath targetDir() const;
const Utils::FilePath targetFile() const;
private:
const QString createFilteredDirEntries(const QStringList &suffixes) const;
const QString createDirArrayEntry(const QString &arrayName, const QStringList &relativePaths) const;
const Utils::FilePath selectTargetFile(const Utils::FilePath &uiFilePath = {});
bool isStandardStructure(const Utils::FilePath &projectDir) const;
bool isDirAcceptable(const Utils::FilePath &dir, const Utils::FilePath &uiFile);
const QString createContentDirEntries(const QString &containerName,
const QStringList &suffixes) const;
const Utils::FilePath findInDirTree(const Utils::FilePath &dir,
const QStringList &suffixes,
int currentSearchDepth = 0) const;
const QStringList findContentDirs(const QStringList &suffixes) const;
private:
Utils::FilePath m_targetDir;
Utils::FilePath m_targetFile;
};
} // GenerateQmlProject
} // QmlProjectManager

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/projectfiletemplates">
<file>qmlproject.tpl</file>
</qresource>
</RCC>

View File

@@ -54,4 +54,13 @@ QtcPlugin {
"cmakeprojectconverterdialog.cpp", "cmakeprojectconverterdialog.h",
]
}
Group {
name: "QML Project File Generator"
prefix: "qmlprojectgen/"
files: [
"qmlprojectgenerator.cpp", "qmlprojectgenerator.h",
"templates.qrc"
]
}
}

View File

@@ -31,6 +31,7 @@
#include "projectfilecontenttools.h"
#include "cmakegen/cmakeprojectconverter.h"
#include "cmakegen/generatecmakelists.h"
#include "qmlprojectgen/qmlprojectgenerator.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
@@ -390,17 +391,12 @@ void QmlProjectPlugin::initializeQmlLandingPage()
void QmlProjectPlugin::displayQmlLandingPage()
{
const QString qtVersionString = ProjectFileContentTools::qtVersion(projectFilePath());
const QString qdsVersionString = ProjectFileContentTools::qdsVersion(projectFilePath());
if (!d->landingPage)
initializeQmlLandingPage();
updateQmlLandingPageProjectInfo(projectFilePath());
d->landingPage->setQdsInstalled(qdsInstallationExists());
d->landingPage->setProjectFileExists(projectFilePath().exists());
d->landingPage->setCmakeResources(ProjectFileContentTools::rootCmakeFiles());
d->landingPage->setQtVersion(qtVersionString);
d->landingPage->setQdsVersion(qdsVersionString);
d->landingPage->show();
}
@@ -469,7 +465,24 @@ void QmlProjectPlugin::generateCmake()
void QmlProjectPlugin::generateProjectFile()
{
qWarning() << "TODO generate .qmlproject";
GenerateQmlProject::QmlProjectFileGenerator generator;
Core::IEditor *editor = Core::EditorManager::currentEditor();
if (editor)
if (generator.prepareForUiQmlFile(editor->document()->filePath()))
if (generator.execute())
updateQmlLandingPageProjectInfo(generator.targetFile());
}
void QmlProjectPlugin::updateQmlLandingPageProjectInfo(const Utils::FilePath &projectFile)
{
if (d->landingPage) {
const QString qtVersionString = ProjectFileContentTools::qtVersion(projectFile);
const QString qdsVersionString = ProjectFileContentTools::qdsVersion(projectFile);
d->landingPage->setProjectFileExists(projectFile.exists());
d->landingPage->setQtVersion(qtVersionString);
d->landingPage->setQdsVersion(qdsVersionString);
}
}
Utils::FilePath QmlProjectPlugin::projectFilePath()

View File

@@ -66,6 +66,7 @@ private:
void initializeQmlLandingPage();
void displayQmlLandingPage();
void hideQmlLandingPage();
void updateQmlLandingPageProjectInfo(const Utils::FilePath &projectFile);
class QmlProjectPluginPrivate *d = nullptr;
};