1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "projecttree.h"
6#include "projectinfodialog.h"
7#include "projectselectionmodel.h"
8#include "projectdelegate.h"
9#include "projectmodel.h"
10#include "transceiver/sendevents.h"
11
12#include "services/project/projectservice.h"
13
14#include "common/common.h"
15
16#include <QDebug>
17#include <QHeaderView>
18#include <QContextMenuEvent>
19#include <QPushButton>
20#include <QDrag>
21#include <QApplication>
22#include <QUrl>
23
24const QString DELETE_MESSAGE_TEXT {QTreeView::tr("The delete operation will be removed from"
25 "the disk and will not be recoverable "
26 "after this operation.\nDelete anyway?")};
27
28using namespace dpfservice;
29
30class ProjectTreePrivate
31{
32 friend class ProjectTree;
33 ProjectModel *itemModel {nullptr};
34 ProjectSelectionModel *sectionModel {nullptr};
35 ProjectDelegate *delegate {nullptr};
36 QPoint startPos;
37 int itemDepth(const QStandardItem *item)
38 {
39 int depth = 0;
40 const QStandardItem *current = item;
41 while (current->parent()) {
42 current = current->parent();
43 depth ++;
44 }
45 return depth;
46 }
47};
48
49ProjectTree::ProjectTree(QWidget *parent)
50 : QTreeView (parent)
51 , d(new ProjectTreePrivate)
52{
53 setEditTriggers(QTreeView::NoEditTriggers); //节点不能编辑
54 setSelectionBehavior(QTreeView::SelectRows); //一次选中整行
55 setSelectionMode(QTreeView::SingleSelection); //单选,配合上面的整行就是一次选单行
56 setFocusPolicy(Qt::NoFocus); //去掉鼠标移到节点上时的虚线框
57 this->header()->hide();
58
59 d->itemModel = new ProjectModel(this);
60 setModel(d->itemModel);
61
62 // 右键菜单创建
63 QObject::connect(this, &ProjectTree::itemMenuRequest,
64 this, &ProjectTree::doItemMenuRequest);
65
66
67 // 双击操作
68 QObject::connect(this, &ProjectTree::doubleClicked,
69 this, &ProjectTree::doDoubleClieked);
70
71 d->sectionModel = new ProjectSelectionModel(d->itemModel);
72 setSelectionModel(d->sectionModel);
73
74 d->delegate = new ProjectDelegate(this);
75 setItemDelegate(d->delegate);
76 this->setDragEnabled(true);
77}
78
79ProjectTree::~ProjectTree()
80{
81 if (d) {
82 delete d;
83 }
84}
85
86void ProjectTree::activeProjectInfo(const ProjectInfo &info)
87{
88 int rowCount = d->itemModel->rowCount();
89 for (int currRow = 0; currRow < rowCount; currRow ++) {
90 auto currItem = d->itemModel->item(currRow, 0);
91 if (currItem) {
92 auto currInfo = ProjectInfo::get(ProjectGenerator::root(currItem));
93 if (currInfo.language() == info.language()
94 && currInfo.workspaceFolder() == info.workspaceFolder()
95 && currInfo.kitName() == info.kitName()) {
96 doActiveProject(currItem);
97 }
98 }
99 }
100}
101
102void ProjectTree::activeProjectInfo(const QString &kitName,
103 const QString &language,
104 const QString &workspace)
105{
106 int rowCount = d->itemModel->rowCount();
107 for (int currRow = 0; currRow < rowCount; currRow ++) {
108 auto currItem = d->itemModel->item(currRow, 0);
109 if (currItem) {
110 auto currInfo = ProjectInfo::get(ProjectGenerator::root(currItem));
111 if (currInfo.language() == language
112 && currInfo.workspaceFolder() == workspace
113 && currInfo.kitName() == kitName) {
114 doActiveProject(currItem);
115 }
116 }
117 }
118}
119
120void ProjectTree::appendRootItem(QStandardItem *root)
121{
122 if (!root)
123 return;
124
125 // 发送工程创建信号
126 using namespace dpfservice;
127 auto info = ProjectInfo::get(ProjectGenerator::root(root));
128
129 // 添加工程节点
130 QStandardItemModel *model = static_cast<QStandardItemModel*>(QTreeView::model());
131 if (model)
132 model->appendRow(root);
133
134 // 发送工程节点已创建信号
135 SendEvents::projectCreated(info);
136
137 // 激活当前工程节点
138 doActiveProject(root);
139}
140
141void ProjectTree::removeRootItem(QStandardItem *root)
142{
143 this->viewport()->setUpdatesEnabled(false);
144
145 ProjectInfo info = ProjectInfo::get(ProjectGenerator::root(root));
146
147 // 从Model中移除
148 this->takeRootItem(root);
149
150 // 从生成器中删除
151 using namespace dpfservice;
152 auto &ctx = dpfInstance.serviceContext();
153 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
154 if (!projectService)
155 return;
156
157 auto generator = projectService->createGenerator<ProjectGenerator>(info.kitName());
158 if (generator)
159 generator->removeRootItem(root);
160
161 // 发送工程删除信号
162 SendEvents::projectDeleted(info);
163
164 // 始终保持首选项
165 int rowCount = d->itemModel->rowCount();
166 if ( 0 < rowCount) { // 存在其他工程时
167 auto index = d->itemModel->index(0, 0);
168 doActiveProject(d->itemModel->itemFromIndex(index));
169 }
170
171 this->viewport()->setUpdatesEnabled(true);
172}
173
174void ProjectTree::takeRootItem(QStandardItem *root)
175{
176 // 从展示的模型中删除
177 QModelIndex index = d->itemModel->indexFromItem(root);
178 d->itemModel->takeRow(index.row());
179}
180
181void ProjectTree::doItemMenuRequest(QStandardItem *item, QContextMenuEvent *event)
182{
183 auto rootItem = ProjectGenerator::root(item);
184 QMenu *menu = nullptr;
185
186 if (rootItem == item) {
187 menu = rootMenu(rootItem);
188 } else {
189 menu = childMenu(rootItem, item);
190 }
191
192 if (menu) {
193 menu->move(event->globalPos());
194 menu->exec();
195 delete menu;
196 }
197}
198
199void ProjectTree::expandedProjectDepth(const QStandardItem *root, int depth)
200{
201 if (!root)
202 return;
203
204 if (d->itemDepth(root) < depth) { //满足深度
205 expand(d->itemModel->indexFromItem(root));
206 for(int i = 0; i < root->rowCount(); i++) {
207 QStandardItem * childitem = root->child(i);
208 if (root->hasChildren()) {
209 expandedProjectDepth(childitem, depth);
210 }
211 }
212 }
213}
214
215void ProjectTree::expandedProjectAll(const QStandardItem *root)
216{
217 if (!root)
218 return;
219
220 expand(d->itemModel->indexFromItem(root));
221 if (root->hasChildren()) {
222 for(int i = 0; i < root->rowCount(); i++) {
223 QStandardItem * childitem = root->child(i);
224 expandedProjectAll(childitem);
225 }
226 }
227}
228
229QList<dpfservice::ProjectInfo> ProjectTree::getAllProjectInfo()
230{
231 using namespace dpfservice;
232 QList<ProjectInfo> result;
233 for (int row = 0; row < d->itemModel->rowCount(); row++) {
234 result << ProjectInfo::get(d->itemModel->index(row, 0));
235 }
236 return result;
237}
238
239ProjectInfo ProjectTree::getProjectInfo(const QString &kitName, const QString &workspace)
240{
241 ProjectInfo projectInfo;
242 for (int row = 0; row < d->itemModel->rowCount(); row++) {
243 ProjectInfo info = ProjectInfo::get(d->itemModel->index(row, 0));
244 if (kitName == info.kitName() && workspace == info.workspaceFolder()) {
245 projectInfo = info;
246 break;
247 }
248 }
249 return projectInfo;
250}
251
252ProjectInfo ProjectTree::getActiveProjectInfo() const
253{
254 ProjectInfo projectInfo;
255 auto activeProject = d->delegate->getActiveProject();
256 if(activeProject.isValid()) {
257 projectInfo = ProjectInfo::get(activeProject);
258 }
259 return projectInfo;
260}
261
262void ProjectTree::contextMenuEvent(QContextMenuEvent *event)
263{
264 QTreeView::contextMenuEvent(event);
265 QModelIndex index = indexAt(event->pos());
266 selectionModel()->select(index, QItemSelectionModel::SelectCurrent);
267 indexMenuRequest(index, event);
268 itemMenuRequest(d->itemModel->itemFromIndex(index), event);
269}
270
271void ProjectTree::mousePressEvent(QMouseEvent *event)
272{
273 if (event->button() == Qt::LeftButton)
274 d->startPos = event->pos();
275 QTreeView::mousePressEvent(event);
276}
277
278void ProjectTree::mouseMoveEvent(QMouseEvent *event)
279{
280 if (event->buttons() & Qt::LeftButton) {
281 int distance = (event->pos() - d->startPos).manhattanLength();
282 if (distance >= QApplication::startDragDistance())
283 performDrag();
284 }
285 QTreeView::mouseMoveEvent(event);
286}
287
288QMenu *ProjectTree::childMenu(const QStandardItem *root, const QStandardItem *child)
289{
290 QMenu *menu = nullptr;
291 QString toolKitName = ProjectInfo::get(root).kitName();
292 // 获取支持右键菜单生成器
293 auto &ctx = dpfInstance.serviceContext();
294 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
295 if (projectService->supportGeneratorName<ProjectGenerator>().contains(toolKitName)) {
296 menu = projectService->createGenerator<ProjectGenerator>(toolKitName)->createItemMenu(child);
297 }
298 if (!menu)
299 menu = new QMenu();
300
301
302 QAction *newDocAction = new QAction(tr("New Document"));
303 menu->addAction(newDocAction);
304 QObject::connect(newDocAction, &QAction::triggered, this, [=](){
305 actionNewDocument(child);
306 });
307
308 bool isDir = false;
309 QModelIndex index = d->itemModel->indexFromItem(child);
310 QFileInfo info(index.data(Qt::ToolTipRole).toString());
311 if (info.isDir()) {
312 isDir = true;
313 }
314
315 QAction *deleteDocAction = new QAction(tr("Delete Document"));
316 menu->addAction(deleteDocAction);
317 QObject::connect(deleteDocAction, &QAction::triggered, this, [=](){
318 actionDeleteDocument(child);
319 });
320 if (isDir) {
321 deleteDocAction->setEnabled(false);
322 }
323 return menu;
324}
325
326QMenu *ProjectTree::rootMenu(QStandardItem *root)
327{
328 QMenu * menu = nullptr;
329 QString toolKitName = ProjectInfo::get(root).kitName();
330 // 获取支持右键菜单生成器
331 auto &ctx = dpfInstance.serviceContext();
332 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
333 if (projectService->supportGeneratorName<ProjectGenerator>().contains(toolKitName)) {
334 menu = projectService->createGenerator<ProjectGenerator>(toolKitName)->createItemMenu(root);
335 }
336 if (!menu)
337 menu = new QMenu();
338
339 QAction* activeProjectAction = new QAction(QAction::tr("Project Active"), menu);
340 QAction* closeAction = new QAction(QAction::tr("Project Close"), menu);
341 QAction* propertyAction = new QAction(QAction::tr("Project Info"), menu);
342 QObject::connect(activeProjectAction, &QAction::triggered, activeProjectAction, [=](){doActiveProject(root);});
343 QObject::connect(closeAction, &QAction::triggered, closeAction, [=](){doCloseProject(root);});
344 QObject::connect(propertyAction, &QAction::triggered, propertyAction, [=](){doShowProjectInfo(root);});
345 menu->insertAction(nullptr, activeProjectAction);
346 menu->insertAction(nullptr, closeAction);
347 menu->insertAction(nullptr, propertyAction);
348 return menu;
349}
350
351void ProjectTree::performDrag()
352{
353 QModelIndex index = currentIndex();
354 QStandardItem *item = d->itemModel->itemFromIndex(index);
355 if (item) {
356 QMimeData *mimeData = new QMimeData;
357 QList<QUrl> urls;
358 QString filePath = "file:" + index.data(Qt::ToolTipRole).toString();
359 urls.append(QUrl(filePath));
360 mimeData->setUrls(urls);
361
362 QDrag *drag = new QDrag(this);
363 drag->setMimeData(mimeData);
364 drag->exec();
365 }
366}
367
368void ProjectTree::itemModified(QStandardItem *item, const QList<QStandardItem *> &childs)
369{
370 setUpdatesEnabled(false);
371 auto parentIndex = d->itemModel->indexFromItem(item);
372 int childCount = d->itemModel->rowCount(parentIndex);
373 d->itemModel->removeRows(0, childCount, parentIndex);
374 item->appendRows(childs);
375 setUpdatesEnabled(true);
376}
377
378void ProjectTree::doDoubleClieked(const QModelIndex &index)
379{
380 QFileInfo info(index.data(Qt::ToolTipRole).toString());
381 if (info.exists() && info.isFile()) {
382 QString workspaceFolder, language;
383 QModelIndex rootIndex = ProjectGenerator::root(index);
384 if (rootIndex.isValid()) {
385 auto info = ProjectInfo::get(rootIndex);
386 workspaceFolder = info.workspaceFolder();
387 language = info.language();
388 }
389 editor.openFileWithKey(workspaceFolder, language, info.filePath());
390 }
391}
392
393void ProjectTree::doCloseProject(QStandardItem *root)
394{
395 if (!root && root != ProjectGenerator::root(root))
396 return;
397 auto info = ProjectInfo::get(root);
398 this->removeRootItem(root);
399}
400
401void ProjectTree::doActiveProject(QStandardItem *root)
402{
403 if (!root && root != ProjectGenerator::root(root))
404 return;
405 d->delegate->setActiveProject(d->itemModel->indexFromItem(root));
406 SendEvents::projectActived(ProjectInfo::get(root));
407}
408
409
410void ProjectTree::actionNewDocument(const QStandardItem *item)
411{
412 QDialog *dlg = new QDialog;
413 QLineEdit *edit = new QLineEdit;
414
415 edit->setAlignment(Qt::AlignLeft);
416 dlg->setAttribute(Qt::WA_DeleteOnClose);
417 dlg->setWindowTitle(tr("New Document"));
418 dlg->resize(400, 100);
419
420 QVBoxLayout *vLayout = new QVBoxLayout(dlg);
421 QPushButton *pbtOk = new QPushButton(tr("ok"));
422 pbtOk->setFixedSize(40, 20);
423 vLayout->addWidget(edit);
424 vLayout->addWidget(pbtOk, 0, Qt::AlignCenter);
425
426 QObject::connect(pbtOk, &QPushButton::clicked, dlg, [=](){
427 creatNewDocument(item, edit->text());
428 dlg->close();
429 });
430
431 dlg->exec();
432}
433
434void ProjectTree::actionDeleteDocument(const QStandardItem *item)
435{
436 QModelIndex index = d->itemModel->indexFromItem(item);
437 QFileInfo info(index.data(Qt::ToolTipRole).toString());
438 if (!info.isFile())
439 return;
440
441 bool doDelete = false;
442 auto okCallBack = [&](bool checked) {
443 Q_UNUSED(checked);
444 doDelete = true;
445 };
446
447 QString mess = DELETE_MESSAGE_TEXT + "\n" + info.filePath();
448 ContextDialog::okCancel(mess,
449 DELETE_MESSAGE_TEXT,
450 QMessageBox::Warning,
451 okCallBack,
452 nullptr);
453
454 if (!doDelete)
455 return;
456 QFile(info.filePath()).remove();
457}
458
459void ProjectTree::creatNewDocument(const QStandardItem *item, const QString &fileName)
460{
461 QModelIndex index = d->itemModel->indexFromItem(item);
462 QFileInfo info(index.data(Qt::ToolTipRole).toString());
463 QString workspace, language;
464 QModelIndex rootIndex = ProjectGenerator::root(index);
465 if (rootIndex.isValid()) {
466 auto rootInfo = ProjectInfo::get(rootIndex);
467 workspace = rootInfo.workspaceFolder();
468 language = rootInfo.language();
469 }
470
471 QString filePath;
472 if (info.isDir()) {
473 filePath = info.filePath() + QDir::separator() + fileName;
474 } else if (info.isFile()) {
475 filePath = info.path() + QDir::separator() + fileName;
476 }
477
478 if (QFile::exists(filePath)) {
479 bool doOverWrite = false;
480 auto okCallBack = [&](bool checked) {
481 Q_UNUSED(checked);
482 doOverWrite = true;
483 };
484
485 QString mess = "A file with name " + fileName + " already exists. Would you like to overwrite it?";
486 ContextDialog::okCancel(mess,
487 DELETE_MESSAGE_TEXT,
488 QMessageBox::Warning,
489 okCallBack,
490 nullptr);
491 if (doOverWrite) {
492 QFile::remove(filePath);
493 }
494 }
495
496 QFile file(filePath);
497 if (file.open(QFile::OpenModeFlag::NewOnly)) {
498 file.close();
499 }
500 editor.openFileWithKey(workspace, language, filePath);
501}
502
503void ProjectTree::doShowProjectInfo(QStandardItem *root)
504{
505 if (!root && root != ProjectGenerator::root(root))
506 return;
507
508 ProjectInfoDialog dialog;
509 QString propertyText = "Language: " + ProjectInfo::get(root).language() + "\n"
510 + "KitName: " + ProjectInfo::get(root).kitName() + "\n"
511 + "BuildFolder: " + ProjectInfo::get(root).buildFolder() + "\n"
512 + "WorkspaceFolder: " + ProjectInfo::get(root).workspaceFolder() + "\n"
513 + "BuildType: " + ProjectInfo::get(root).buildType() + "\n"
514 + "BuildProgram: " + "\n " + ProjectInfo::get(root).buildProgram() + "\n"
515 + "ConfigCustomArgs: " + "\n " + ProjectInfo::get(root).configCustomArgs().join(" ") + "\n"
516 + "BuildCustomArgs: " + "\n " + ProjectInfo::get(root).buildCustomArgs().join(" ") + "\n"
517 + "CleanCustomArgs: " + "\n " + ProjectInfo::get(root).cleanCustomArgs().join(" ");
518 dialog.setPropertyText(propertyText);
519 dialog.exec();
520}
521