aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Hartmann <thomas.hartmann@qt.io>2022-05-10 14:23:51 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2022-05-17 15:17:12 +0000
commitde7a7b6ac882bf55fc699563aaadbc7c7b3b2061 (patch)
tree2e4bb4a7d40c41af9a7f5680327d4127927ba684
parent5e31fae6d9b87f47cd95d21bb2309d44632b1e28 (diff)
QmlProject: Allow setting main qml file and main ui.qml file
The .qmlproject file already has the "mainFile" setting which indicates which qml file is run. This patch adds a main ui.qml file that indicates which ui.qml file is opened in the design mode if the .qmlproject is opened. The patch also adds two context menu actions that allow setting the main qml and main.ui.qml files without having to edit the .qmlproject file by hand. Changing the main ui.qml file also checks if the current ui.qml file is used as in the main qml file and if it is, then we switch the component there. For now the actions are only available in QDS. Task-number: QDS-6882 Change-Id: I1c6e19c039036dc635161fa6e06173356dc509aa Reviewed-by: Henning Gründl <henning.gruendl@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
-rw-r--r--src/plugins/qmlprojectmanager/fileformat/qmlprojectfileformat.cpp4
-rw-r--r--src/plugins/qmlprojectmanager/fileformat/qmlprojectitem.h4
-rw-r--r--src/plugins/qmlprojectmanager/qmlproject.cpp195
-rw-r--r--src/plugins/qmlprojectmanager/qmlproject.h10
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectplugin.cpp114
5 files changed, 276 insertions, 51 deletions
diff --git a/src/plugins/qmlprojectmanager/fileformat/qmlprojectfileformat.cpp b/src/plugins/qmlprojectmanager/fileformat/qmlprojectfileformat.cpp
index ce54df3d8d..f385cc7466 100644
--- a/src/plugins/qmlprojectmanager/fileformat/qmlprojectfileformat.cpp
+++ b/src/plugins/qmlprojectmanager/fileformat/qmlprojectfileformat.cpp
@@ -91,6 +91,10 @@ QmlProjectItem *QmlProjectFileFormat::parseProjectFile(const Utils::FilePath &fi
if (mainFileProperty.isValid())
projectItem->setMainFile(mainFileProperty.value.toString());
+ const auto mainUiFileProperty = rootNode->property(QLatin1String("mainUiFile"));
+ if (mainUiFileProperty.isValid())
+ projectItem->setMainUiFile(mainUiFileProperty.value.toString());
+
const auto importPathsProperty = rootNode->property(QLatin1String("importPaths"));
if (importPathsProperty.isValid()) {
QStringList list = importPathsProperty.value.toStringList();
diff --git a/src/plugins/qmlprojectmanager/fileformat/qmlprojectitem.h b/src/plugins/qmlprojectmanager/fileformat/qmlprojectitem.h
index 02916555a7..d437db6114 100644
--- a/src/plugins/qmlprojectmanager/fileformat/qmlprojectitem.h
+++ b/src/plugins/qmlprojectmanager/fileformat/qmlprojectitem.h
@@ -81,6 +81,9 @@ public:
QString mainFile() const { return m_mainFile; }
void setMainFile(const QString &mainFilePath) { m_mainFile = mainFilePath; }
+ QString mainUiFile() const { return m_mainUiFile; }
+ void setMainUiFile(const QString &mainUiFilePath) { m_mainUiFile = mainUiFilePath; }
+
bool widgetApp() const { return m_widgetApp; }
void setWidgetApp(bool widgetApp) { m_widgetApp = widgetApp; }
@@ -107,6 +110,7 @@ protected:
QStringList m_supportedLanguages;
QString m_primaryLanguage;
QString m_mainFile;
+ QString m_mainUiFile;
Utils::EnvironmentItems m_environment;
QVector<QmlProjectContentItem *> m_content; // content property
bool m_forceFreeType = false;
diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp
index 37fb78f033..dc58282e0d 100644
--- a/src/plugins/qmlprojectmanager/qmlproject.cpp
+++ b/src/plugins/qmlprojectmanager/qmlproject.cpp
@@ -135,21 +135,38 @@ QmlProject::QmlProject(const Utils::FilePath &fileName)
disconnect(m_openFileConnection);
if (target && success) {
- Utils::FilePaths uiFiles = getUiQmlFilesForFolder(projectDirectory()
- + "/content");
- if (uiFiles.isEmpty())
- uiFiles = getUiQmlFilesForFolder(projectDirectory());
-
- if (!uiFiles.isEmpty()) {
- Utils::FilePath currentFile;
- if (auto cd = Core::EditorManager::currentDocument())
- currentFile = cd->filePath();
-
- if (currentFile.isEmpty() || !isKnownFile(currentFile))
- QTimer::singleShot(1000, [uiFiles]() {
- Core::EditorManager::openEditor(uiFiles.first(),
+
+ auto target = activeTarget();
+ if (!target)
+ return;
+
+ auto qmlBuildSystem = qobject_cast<QmlProjectManager::QmlBuildSystem *>(
+ target->buildSystem());
+
+ const Utils::FilePath mainUiFile = qmlBuildSystem->mainUiFilePath();
+
+ if (mainUiFile.completeSuffix() == "qi.qml" && mainUiFile.exists()) {
+ QTimer::singleShot(1000, [mainUiFile]() {
+ Core::EditorManager::openEditor(mainUiFile,
Utils::Id());
- });
+ });
+ } else {
+ Utils::FilePaths uiFiles = getUiQmlFilesForFolder(projectDirectory()
+ + "/content");
+ if (uiFiles.isEmpty())
+ uiFiles = getUiQmlFilesForFolder(projectDirectory());
+
+ if (!uiFiles.isEmpty()) {
+ Utils::FilePath currentFile;
+ if (auto cd = Core::EditorManager::currentDocument())
+ currentFile = cd->filePath();
+
+ if (currentFile.isEmpty() || !isKnownFile(currentFile))
+ QTimer::singleShot(1000, [uiFiles]() {
+ Core::EditorManager::openEditor(uiFiles.first(),
+ Utils::Id());
+ });
+ }
}
}
});
@@ -253,6 +270,58 @@ void QmlBuildSystem::parseProject(RefreshOptions options)
}
}
+bool QmlBuildSystem::setFileSettingInProjectFile(const QString &setting, const Utils::FilePath &mainFilePath, const QString &oldFile)
+{
+ // make sure to change it also in the qmlproject file
+ const Utils::FilePath qmlProjectFilePath = project()->projectFilePath();
+ Core::FileChangeBlocker fileChangeBlocker(qmlProjectFilePath);
+ const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(qmlProjectFilePath);
+ TextEditor::TextDocument *document = nullptr;
+ if (!editors.isEmpty()) {
+ document = qobject_cast<TextEditor::TextDocument*>(editors.first()->document());
+ if (document && document->isModified())
+ if (!Core::DocumentManager::saveDocument(document))
+ return false;
+ }
+
+ QString fileContent;
+ QString error;
+ Utils::TextFileFormat textFileFormat;
+ const QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // qml files are defined to be utf-8
+ if (Utils::TextFileFormat::readFile(qmlProjectFilePath, codec, &fileContent, &textFileFormat, &error)
+ != Utils::TextFileFormat::ReadSuccess) {
+ qWarning() << "Failed to read file" << qmlProjectFilePath << ":" << error;
+ }
+
+ const QString settingQmlCode = setting + ":";
+
+ QDir projectDir = project()->projectFilePath().toDir();
+ projectDir.cdUp();
+ const QString relativePath = projectDir.relativeFilePath(mainFilePath.toString());
+
+ if (fileContent.indexOf(settingQmlCode) < 0) {
+ QString addedText = QString("\n %1 \"%2\"\n").arg(settingQmlCode).arg(relativePath);
+ auto index = fileContent.lastIndexOf("}");
+ fileContent.insert(index, addedText);
+ } else {
+ QString originalFileName = oldFile;
+ originalFileName.replace(".", "\\.");
+ const QRegularExpression expression(QString("%1\\s*\"(%2)\"").arg(settingQmlCode).arg(originalFileName));
+
+ const QRegularExpressionMatch match = expression.match(fileContent);
+
+ fileContent.replace(match.capturedStart(1),
+ match.capturedLength(1),
+ relativePath);
+ }
+
+ if (!textFileFormat.writeFile(qmlProjectFilePath, fileContent, &error))
+ qWarning() << "Failed to write file" << qmlProjectFilePath << ":" << error;
+
+ refresh(Everything);
+ return true;
+}
+
void QmlBuildSystem::refresh(RefreshOptions options)
{
ParseGuard guard = guardParsingRun();
@@ -283,11 +352,68 @@ QString QmlBuildSystem::mainFile() const
return QString();
}
+QString QmlBuildSystem::mainUiFile() const
+{
+ if (m_projectItem)
+ return m_projectItem->mainUiFile();
+ return QString();
+}
+
Utils::FilePath QmlBuildSystem::mainFilePath() const
{
return projectDirectory().pathAppended(mainFile());
}
+Utils::FilePath QmlBuildSystem::mainUiFilePath() const
+{
+ return projectDirectory().pathAppended(mainUiFile());
+}
+
+bool QmlBuildSystem::setMainFileInProjectFile(const Utils::FilePath &newMainFilePath)
+{
+
+ return setFileSettingInProjectFile("mainFile", newMainFilePath, mainFile());
+}
+
+bool QmlBuildSystem::setMainUiFileInProjectFile(const Utils::FilePath &newMainUiFilePath)
+{
+ return setMainUiFileInMainFile(newMainUiFilePath)
+ && setFileSettingInProjectFile("mainUiFile", newMainUiFilePath, mainUiFile());
+}
+
+bool QmlBuildSystem::setMainUiFileInMainFile(const Utils::FilePath &newMainUiFilePath)
+{
+ Core::FileChangeBlocker fileChangeBlocker(mainFilePath());
+ const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(mainFilePath());
+ TextEditor::TextDocument *document = nullptr;
+ if (!editors.isEmpty()) {
+ document = qobject_cast<TextEditor::TextDocument*>(editors.first()->document());
+ if (document && document->isModified())
+ if (!Core::DocumentManager::saveDocument(document))
+ return false;
+ }
+
+ QString fileContent;
+ QString error;
+ Utils::TextFileFormat textFileFormat;
+ const QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // qml files are defined to be utf-8
+ if (Utils::TextFileFormat::readFile(mainFilePath(), codec, &fileContent, &textFileFormat, &error)
+ != Utils::TextFileFormat::ReadSuccess) {
+ qWarning() << "Failed to read file" << mainFilePath() << ":" << error;
+ }
+
+ const QString currentMain = QString("%1 {").arg(mainUiFilePath().baseName());
+ const QString newMain = QString("%1 {").arg(newMainUiFilePath.baseName());
+
+ if (fileContent.contains(currentMain))
+ fileContent.replace(currentMain, newMain);
+
+ if (!textFileFormat.writeFile(mainFilePath(), fileContent, &error))
+ qWarning() << "Failed to write file" << mainFilePath() << ":" << error;
+
+ return true;
+}
+
bool QmlBuildSystem::qtForMCUs() const
{
if (m_projectItem)
@@ -663,43 +789,10 @@ bool QmlBuildSystem::deleteFiles(Node *context, const FilePaths &filePaths)
bool QmlBuildSystem::renameFile(Node * context, const FilePath &oldFilePath, const FilePath &newFilePath)
{
if (dynamic_cast<QmlProjectNode *>(context)) {
- if (oldFilePath.endsWith(mainFile())) {
- setMainFile(newFilePath.toString());
-
- // make sure to change it also in the qmlproject file
- const Utils::FilePath qmlProjectFilePath = project()->projectFilePath();
- Core::FileChangeBlocker fileChangeBlocker(qmlProjectFilePath);
- const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(qmlProjectFilePath);
- TextEditor::TextDocument *document = nullptr;
- if (!editors.isEmpty()) {
- document = qobject_cast<TextEditor::TextDocument*>(editors.first()->document());
- if (document && document->isModified())
- if (!Core::DocumentManager::saveDocument(document))
- return false;
- }
-
- QString fileContent;
- QString error;
- Utils::TextFileFormat textFileFormat;
- const QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // qml files are defined to be utf-8
- if (Utils::TextFileFormat::readFile(qmlProjectFilePath, codec, &fileContent, &textFileFormat, &error)
- != Utils::TextFileFormat::ReadSuccess) {
- qWarning() << "Failed to read file" << qmlProjectFilePath << ":" << error;
- }
-
- // find the mainFile and do the file name with brackets in a capture group and mask the . with \.
- QString originalFileName = oldFilePath.fileName();
- originalFileName.replace(".", "\\.");
- const QRegularExpression expression(QString("mainFile:\\s*\"(%1)\"").arg(originalFileName));
- const QRegularExpressionMatch match = expression.match(fileContent);
-
- fileContent.replace(match.capturedStart(1), match.capturedLength(1), newFilePath.fileName());
-
- if (!textFileFormat.writeFile(qmlProjectFilePath, fileContent, &error))
- qWarning() << "Failed to write file" << qmlProjectFilePath << ":" << error;
-
- refresh(Everything);
- }
+ if (oldFilePath.endsWith(mainFile()))
+ return setMainFileInProjectFile(newFilePath);
+ if (oldFilePath.endsWith(mainUiFile()))
+ return setMainUiFileInProjectFile(newFilePath);
return true;
}
diff --git a/src/plugins/qmlprojectmanager/qmlproject.h b/src/plugins/qmlprojectmanager/qmlproject.h
index 78a30187f6..a5ce067be6 100644
--- a/src/plugins/qmlprojectmanager/qmlproject.h
+++ b/src/plugins/qmlprojectmanager/qmlproject.h
@@ -77,7 +77,13 @@ public:
Utils::FilePath canonicalProjectDir() const;
QString mainFile() const;
+ QString mainUiFile() const;
Utils::FilePath mainFilePath() const;
+ Utils::FilePath mainUiFilePath() const;
+
+ bool setMainFileInProjectFile(const Utils::FilePath &newMainFilePath);
+ bool setMainUiFileInProjectFile(const Utils::FilePath &newMainUiFilePath);
+ bool setMainUiFileInMainFile(const Utils::FilePath &newMainUiFilePath);
bool qtForMCUs() const;
bool qt6Project() const;
@@ -116,6 +122,10 @@ public:
void parseProject(RefreshOptions options);
private:
+ bool setFileSettingInProjectFile(const QString &setting,
+ const Utils::FilePath &mainFilePath,
+ const QString &oldFile);
+
std::unique_ptr<QmlProjectItem> m_projectItem;
Utils::FilePath m_canonicalProjectDir;
bool m_blockFilesUpdate = false;
diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
index bc7b6f3bde..ff58349dc0 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
+++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
@@ -28,14 +28,20 @@
#include "qmlprojectconstants.h"
#include "qmlprojectrunconfiguration.h"
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/fileiconprovider.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
+#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectnodes.h>
+#include <projectexplorer/projecttree.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/session.h>
+#include <projectexplorer/target.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
@@ -47,6 +53,7 @@
#include <utils/infobar.h>
#include <utils/qtcprocess.h>
+#include <QAction>
#include <QMessageBox>
#include <QPushButton>
#include <QTimer>
@@ -197,6 +204,23 @@ void QmlProjectPlugin::openInQDSWithProject(const Utils::FilePath &filePath)
}
}
+static QmlBuildSystem *qmlBuildSystemforFileNode(const FileNode *fileNode)
+{
+ if (!fileNode)
+ return nullptr;
+
+ if (QmlProject *qmlProject = qobject_cast<QmlProject*>(fileNode->getProject())) {
+ auto target = qmlProject->activeTarget();
+ if (!target)
+ return nullptr;
+
+ return qobject_cast<QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
+
+ }
+
+ return nullptr;
+}
+
bool QmlProjectPlugin::initialize(const QStringList &, QString *errorMessage)
{
Q_UNUSED(errorMessage)
@@ -206,6 +230,7 @@ bool QmlProjectPlugin::initialize(const QStringList &, QString *errorMessage)
if (!qmlDesignerEnabled()) {
connect(Core::EditorManager::instance(),
&Core::EditorManager::currentEditorChanged,
+ this,
[this](Core::IEditor *editor) {
QmlJS::ModelManagerInterface *modelManager
= QmlJS::ModelManagerInterface::instance();
@@ -258,6 +283,95 @@ bool QmlProjectPlugin::initialize(const QStringList &, QString *errorMessage)
ProjectManager::registerProjectType<QmlProject>(QmlJSTools::Constants::QMLPROJECT_MIMETYPE);
Core::FileIconProvider::registerIconOverlayForSuffix(":/qmlproject/images/qmlproject.png",
"qmlproject");
+
+ if (QmlProject::isQtDesignStudio()) {
+ Core::ActionContainer *menu = Core::ActionManager::actionContainer(
+ ProjectExplorer::Constants::M_FILECONTEXT);
+ QAction *mainfileAction = new QAction(tr("Set as main .qml file"), this);
+ mainfileAction->setEnabled(false);
+
+ connect(mainfileAction, &QAction::triggered, this, []() {
+ const Node *currentNode = ProjectTree::currentNode();
+ if (!currentNode || !currentNode->asFileNode()
+ || currentNode->asFileNode()->fileType() != FileType::QML)
+ return;
+
+ const Utils::FilePath file = currentNode->filePath();
+
+ QmlBuildSystem *buildSystem = qmlBuildSystemforFileNode(currentNode->asFileNode());
+ if (buildSystem)
+ buildSystem->setMainFileInProjectFile(file);
+ });
+
+ menu->addAction(Core::ActionManager::registerAction(
+ mainfileAction,
+ "QmlProject.setMainFile",
+ Core::Context(ProjectExplorer::Constants::C_PROJECT_TREE)),
+ ProjectExplorer::Constants::G_FILE_OTHER);
+ mainfileAction->setVisible(false);
+ connect(ProjectTree::instance(),
+ &ProjectTree::currentNodeChanged,
+ mainfileAction,
+ [mainfileAction](Node *node) {
+ const FileNode *fileNode = node ? node->asFileNode() : nullptr;
+
+ const bool isVisible = fileNode && fileNode->fileType() == FileType::QML
+ && fileNode->filePath().completeSuffix() == "qml";
+
+ mainfileAction->setVisible(isVisible);
+
+ if (!isVisible)
+ return;
+
+ QmlBuildSystem *buildSystem = qmlBuildSystemforFileNode(fileNode);
+
+ if (buildSystem)
+ mainfileAction->setEnabled(buildSystem->mainFilePath()
+ != fileNode->filePath());
+ });
+
+ QAction *mainUifileAction = new QAction(tr("Set as main .ui.qml file"), this);
+ mainUifileAction->setEnabled(false);
+
+ connect(mainUifileAction, &QAction::triggered, this, []() {
+ const Node *currentNode = ProjectTree::currentNode();
+ if (!currentNode || !currentNode->asFileNode()
+ || currentNode->asFileNode()->fileType() != FileType::QML)
+ return;
+
+ const Utils::FilePath file = currentNode->filePath();
+
+ QmlBuildSystem *buildSystem = qmlBuildSystemforFileNode(currentNode->asFileNode());
+ if (buildSystem)
+ buildSystem->setMainUiFileInProjectFile(file);
+ });
+
+ menu->addAction(Core::ActionManager::registerAction(
+ mainUifileAction,
+ "QmlProject.setMainUIFile",
+ Core::Context(ProjectExplorer::Constants::C_PROJECT_TREE)),
+ ProjectExplorer::Constants::G_FILE_OTHER);
+ mainUifileAction->setVisible(false);
+ connect(ProjectTree::instance(),
+ &ProjectTree::currentNodeChanged,
+ mainUifileAction,
+ [mainUifileAction](Node *node) {
+ const FileNode *fileNode = node ? node->asFileNode() : nullptr;
+ const bool isVisible = fileNode && fileNode->fileType() == FileType::QML
+ && fileNode->filePath().completeSuffix() == "ui.qml";
+
+ mainUifileAction->setVisible(isVisible);
+
+ if (!isVisible)
+ return;
+
+ QmlBuildSystem *buildSystem = qmlBuildSystemforFileNode(fileNode);
+ if (buildSystem)
+ mainUifileAction->setEnabled(buildSystem->mainUiFilePath()
+ != fileNode->filePath());
+ });
+ }
+
return true;
} // namespace Internal