1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "projectgenerator.h"
6#include "projectservice.h"
7#include "window/windowservice.h"
8#include "window/windowelement.h"
9
10#include <QFileDialog>
11
12/*!
13 * \brief The ProjectGenerator class 工程生成器对象
14 * 该类主要功能为生成各类工程提供接口, 该类与ProjectService接口分开
15 * 达到接口隔离和适应插件组合变化
16 * 模式1. 仅仅包含ProjectGenerator的插件(独立功能的插件)
17 * 模式2. 包含ProjectGenerator、主界面框架组件发布、流程执行(倾向于业务流程)
18 * ProjectService 面向工程核心(ProjectCore) 提供对外接口
19 * ProjectGenerator 面向工程扩展实现其他插件对外的接口
20 * ProjectCore <-> ProjectService <-> [outside plugin]
21 */
22
23
24QStringList dpfservice::ProjectGenerator::supportLanguages()
25{
26 return {};
27}
28
29QStringList dpfservice::ProjectGenerator::supportFileNames()
30{
31 return {};
32}
33
34QAction *dpfservice::ProjectGenerator::openProjectAction(const QString &language,
35 const QString &actionText)
36{
37 auto result = new QAction(actionText);
38 QObject::connect(result, &QAction::triggered, [=](){
39 QString iniPath = CustomPaths::user(CustomPaths::Flags::Configures)
40 + QDir::separator() + QString("project_record.support");
41 QSettings setting(iniPath, QSettings::IniFormat);
42 QString lastPath = setting.value(language + "-" + actionText).toString();
43 if (lastPath.isEmpty()) {
44 lastPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
45 }
46
47 QFileDialog fileDialog;
48 fileDialog.setFileMode(QFileDialog::Directory);
49 fileDialog.setOption(QFileDialog::DontResolveSymlinks);
50 fileDialog.setWindowTitle(QFileDialog::tr("Open %0 Project Directory").arg(language));
51 fileDialog.setDirectory(lastPath);
52 fileDialog.setWindowFlags(fileDialog.windowFlags() | Qt::WindowStaysOnTopHint);
53 if (fileDialog.exec() == QDialog::Accepted) {
54 QString workspace = fileDialog.selectedUrls().first().path();
55 setting.setValue(language + "-" + actionText, workspace); // save open history
56 if (canOpenProject(actionText, language, workspace))
57 doProjectOpen(language, actionText, workspace);
58 }
59 });
60 return result;
61}
62
63/*!
64 * \brief dpfservice::ProjectGenerator::projectIsOpened
65 * check project is opened, default can't reopen opened project
66 * \param kitName project kitName
67 * \param language project language
68 * \param projectPath to open project path
69 * \return is opened return true, else return false;
70 */
71bool dpfservice::ProjectGenerator::canOpenProject(const QString &kitName,
72 const QString &language,
73 const QString &workspace)
74{
75 if (isOpenedProject(kitName, language, workspace))
76 return false;
77
78 QStringList fileNames = supportFileNames();
79 if (!fileNames.isEmpty()) {
80 for (auto filename : fileNames) {
81 if (QDir(workspace).exists(filename))
82 return true;
83 }
84 ContextDialog::ok(QDialog::tr("Cannot open the project!\n"
85 "not exists support files: %0")
86 .arg(supportFileNames().join(",")));
87 return false;
88 }
89 return true;
90}
91
92bool dpfservice::ProjectGenerator::isOpenedProject(const QString &kitName,
93 const QString &language,
94 const QString &workspace)
95{
96 auto &ctx = dpfInstance.serviceContext();
97 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
98 auto proInfos = projectService->getAllProjectInfo();
99 for (auto info : proInfos) {
100 if (info.kitName() == kitName
101 && info.language() == language
102 && info.workspaceFolder() == workspace) {
103 return true;
104 }
105 }
106 return false;
107}
108
109void dpfservice::ProjectGenerator::doProjectOpen(const QString &language,
110 const QString &actionText,
111 const QString &workspace)
112{
113 using namespace dpfservice;
114 auto &ctx = dpfInstance.serviceContext();
115 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
116
117 if (!projectService)
118 return;
119
120 auto generator = projectService->createGenerator<ProjectGenerator>(actionText);
121 if (!generator)
122 return;
123
124 auto widget = generator->configureWidget(language, workspace);
125 if (widget) {
126 widget->exec();
127 }
128}
129
130/*!
131 * \brief configure 生成器配置界面,子类需要重载实现
132 * 按照一般的做法,当前界面应当生成配置文件与工程路径绑定,
133 * 在界面上选中、输入信息存入配置文件
134 * \param projectPath 工程路径
135 * \return 返回工程配置界面
136 */
137QDialog *dpfservice::ProjectGenerator::configureWidget(const QString &language, const QString &workspace)
138{
139 Q_UNUSED(language)
140 Q_UNUSED(workspace)
141 return nullptr;
142}
143
144/*!
145 * \brief configure 执行生成器的过程
146 * 按照生成器配置界面约定规则,实现执行过程,
147 * 例如在cmake中,需要执行configure指令生成cache等相关文件
148 * 执行该函数,应当首先确保前置条件满足(配置文件已生成)。
149 * \param projectPath 工程路径
150 * \return 过程执行结果
151 */
152bool dpfservice::ProjectGenerator::configure(const dpfservice::ProjectInfo &projectInfo) {
153
154 // "filePath", "kitName", "language", "workspace"
155 recent.saveOpenedProject(projectInfo.kitName(),
156 projectInfo.language(),
157 projectInfo.workspaceFolder());
158 navigation.doSwitch(MWNA_EDIT);
159
160 editor.switchWorkspace(MWCWT_PROJECTS);
161
162 if (!projectInfo.workspaceFolder().isEmpty()) {
163 dpf::Event event;
164 event.setTopic(T_COLLABORATORS);
165 event.setData(D_OPEN_REPOS);
166 event.setProperty(P_WORKSPACEFOLDER, projectInfo.workspaceFolder());
167 dpf::EventCallProxy::instance().pubEvent(event);
168 }
169
170 Generator::started(); // emit starded
171
172 return false;
173}
174
175/*!
176 * \brief createRootItem 创建文件树路径,子类需要重载实现
177 * 执行该函数应当首先确定前置条件的满足,比如已经执行了生成器的过程。
178 * \param ProjectInfo 工程信息
179 * \return
180 */
181QStandardItem *dpfservice::ProjectGenerator::createRootItem(const dpfservice::ProjectInfo &info)
182{
183 QIcon icon = CustomIcons::icon(info.workspaceFolder());
184 QString displyName = QFileInfo(info.workspaceFolder()).fileName();
185 QString tooltip = info.workspaceFolder();
186 auto rootItem = new QStandardItem(icon, displyName);
187 rootItem->setToolTip(tooltip);
188 return rootItem;
189}
190
191/*!
192 * \brief removeRootItem 删除文件树根节点,子类需要重载实现
193 * 执行函数如果存在异步加载的情况,则需要通过该接口实现异步释放
194 * \param info
195 */
196void dpfservice::ProjectGenerator::removeRootItem(QStandardItem *root)
197{
198 Q_UNUSED(root)
199}
200
201/*!
202 * \brief createIndexMenu 文件树index点击时触发,子类需要重载实现
203 * 文件树中右键触发的创建Menu,传入树中QModelIndex,它与Item数据相通(Qt规范)
204 * QMenu对象在插件使用方会被释放
205 * \param index 索引数据
206 * \return
207 */
208QMenu *dpfservice::ProjectGenerator::createItemMenu(const QStandardItem *item)
209{
210 Q_UNUSED(item)
211 return nullptr;
212}
213
214/*!
215 * \brief root 获取子节点的根节点
216 * \param child 子节点
217 * \return 根节点
218 */
219QStandardItem *dpfservice::ProjectGenerator::root(QStandardItem *child)
220{
221 if (!child)
222 return nullptr;
223 QStandardItem *parent = child->parent();
224 if (parent)
225 return root(parent);
226 return child;
227}
228
229/*!
230 * \brief root 获取子节点的根节点
231 * \param child 子节点
232 * \return 根节点
233 */
234const QModelIndex dpfservice::ProjectGenerator::root(const QModelIndex &child)
235{
236 if (!child.isValid())
237 return {};
238 const QModelIndex parent = child.parent();
239 if (parent.isValid())
240 return root(parent);
241 return child;
242}
243