// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlprojectgenerator.h" #include "../cmakegen/cmakewriter.h" #include "../qmlprojectmanagertr.h" #include #include #include 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::CMakeWriter::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(), Tr::tr("Project File Generated"), Tr::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( Tr::tr("Select File Location"), suggestedDir, Tr::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(), Tr::tr("Invalid Directory"), Tr::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(), Tr::tr("Problem"), Tr::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