1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "treeview.h"
6#include "transceiver/sendevents.h"
7
8#include "common/common.h"
9
10#include <QHeaderView>
11#include <QMenu>
12#include <QDebug>
13#include <QContextMenuEvent>
14#include <QFileSystemModel>
15#include <QStack>
16
17const QString NEW_DOCUMENT_NAME = "NewDocument.txt";
18const QString NEW_FOLDER_NAME = "NewFolder";
19const QString DELETE_MESSAGE_TEXT {QTreeView::tr("The delete operation will be removed from"
20 "the disk and will not be recoverable "
21 "after this operation.\nDelete anyway?")};
22
23const QString DELETE_WINDOW_TEXT {QTreeView::tr("Delete Warning")};
24
25class TreeViewPrivate
26{
27 friend class TreeView;
28 QFileSystemModel *model {nullptr};
29 QMenu* menu {nullptr};
30 QStack<QStringList> moveToTrashStack;
31 dpfservice::ProjectInfo proInfo;
32};
33
34TreeView::TreeView(QWidget *parent)
35 : QTreeView (parent)
36 , d (new TreeViewPrivate)
37{
38 d->model = new QFileSystemModel(this);
39 d->menu = new QMenu(this);
40 setModel(d->model);
41 header()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
42 QObject::connect(this, &QTreeView::doubleClicked, this, &TreeView::doDoubleClicked);
43}
44
45TreeView::~TreeView()
46{
47 if (d) {
48 delete d;
49 }
50}
51
52void TreeView::setProjectInfo(const dpfservice::ProjectInfo &proInfo)
53{
54 d->proInfo = proInfo;
55 d->model->setRootPath(proInfo.workspaceFolder());
56 auto index = d->model->index(proInfo.workspaceFolder());
57 QTreeView::expand(index);
58 QTreeView::setRootIndex(index);
59 emit rootPathChanged(proInfo.workspaceFolder());
60}
61
62void TreeView::selOpen()
63{
64 QModelIndexList indexs = selectedIndexes();
65 QSet<QString> countPaths;
66 for (auto index : indexs) {
67 countPaths << d->model->filePath(index);
68 }
69
70 for (auto path : countPaths) {
71 if (QFileInfo(path).isFile())
72 editor.openFile(path);
73 }
74}
75
76void TreeView::selMoveToTrash()
77{
78 QModelIndexList indexs = selectedIndexes();
79 QSet<QString> countPaths;
80 for (auto index : indexs) {
81 countPaths << d->model->filePath(index);
82 }
83
84 QStringList errFilePaths;
85 QStringList okFilePaths;
86 bool hasError = false;
87 for (auto path : countPaths) {
88 bool currErr = !FileOperation::doMoveMoveToTrash(path);
89 if (currErr){
90 errFilePaths << path;
91 hasError = true;
92 } else {
93 okFilePaths << path;
94 }
95 }
96
97 if (!hasError) {
98 d->moveToTrashStack.push(okFilePaths);
99 } else {
100 QString errMess;
101 for (auto errFilePath : errFilePaths) {
102 errMess = QTreeView::tr("Error, Can't move to trash: ") + "\n"
103 + errFilePath + "\n";
104 }
105 ContextDialog::ok(errMess);
106 }
107}
108
109void TreeView::selRemove()
110{
111 QModelIndexList indexs = selectedIndexes();
112 QSet<QString> countPaths;
113 for (auto index : indexs) {
114 countPaths << d->model->filePath(index);
115 }
116
117 bool doDeleta = false;
118 auto okCallBack = [&](bool checked) {
119 Q_UNUSED(checked);
120 doDeleta = true;
121 };
122
123 QString mess = DELETE_MESSAGE_TEXT + "\n";
124 for (auto path : countPaths) {
125 mess += path + "\n";
126 }
127
128 ContextDialog::okCancel(mess,
129 DELETE_WINDOW_TEXT,
130 QMessageBox::Warning,
131 okCallBack,
132 nullptr);
133
134 if (!doDeleta)
135 return;
136
137 bool hasError = false;
138 QStringList errFilePaths;
139 for (auto currPath : countPaths){
140 bool currErr = !FileOperation::doRemove(currPath);
141 if (currErr){
142 errFilePaths << currPath;
143 hasError = true;
144 }
145 }
146
147 if (hasError) {
148 QString errMess;
149 for (auto errFilePath : errFilePaths) {
150 errMess = QTreeView::tr("Error, Can't move to trash: ") + "\n"
151 + errFilePath + "\n";
152 }
153 ContextDialog::ok(errMess);
154 }
155}
156
157void TreeView::selNewDocument()
158{
159 QModelIndexList indexs = selectedIndexes();
160 bool hasErr = false;
161 QString errString;
162 if (indexs.size() == 1) {
163 QString filePath = d->model->filePath(indexs[0]);
164 QFileInfo info(filePath);
165 if (info.isDir()) {
166 hasErr = !FileOperation::doNewDocument(filePath, NEW_DOCUMENT_NAME);
167 if (hasErr)
168 errString = QTreeView::tr("Error: Can't create New Document");
169 } else {
170 hasErr = true;
171 errString = QTreeView::tr("Error: Create New Document, parent not's dir");
172 }
173 }
174
175 if (hasErr)
176 ContextDialog::ok(errString);
177}
178
179void TreeView::selNewFolder()
180{
181 QModelIndexList indexs = selectedIndexes();
182 bool hasErr = false;
183 QString errString;
184 if (indexs.size() > 0) {
185 QString filePath = d->model->filePath(indexs[0]);
186 QFileInfo info(filePath);
187 if (info.isDir()) {
188 hasErr = !FileOperation::doNewFolder(filePath, NEW_FOLDER_NAME);
189 if (hasErr)
190 errString = QTreeView::tr("Error: Can't create new folder");
191 } else {
192 hasErr = true;
193 errString = QTreeView::tr("Error: Create new folder, parent not's dir");
194 }
195 }
196
197 if (hasErr)
198 ContextDialog::ok(errString);
199}
200
201void TreeView::recoverFromTrash()
202{
203 if (!d->moveToTrashStack.isEmpty()) {
204 auto filePaths = d->moveToTrashStack.pop();
205 for (auto filePath : filePaths) {
206 FileOperation::doRecoverFromTrash(filePath);
207 }
208 }
209}
210
211void TreeView::doDoubleClicked(const QModelIndex &index)
212{
213 QString filePath = d->model->filePath(index);
214 if (QFileInfo(filePath).isFile())
215 editor.openFile(filePath);
216}
217
218void TreeView::contextMenuEvent(QContextMenuEvent *event)
219{
220 QModelIndex index = QTreeView::indexAt(event->pos());
221 if (index.isValid()) {
222 d->menu = createContextMenu(selectedIndexes());
223 } else {
224 d->menu = createEmptyMenu();
225 }
226 d->menu->exec(viewport()->mapToGlobal(event->pos()));
227}
228
229QMenu *TreeView::createContextMenu(const QModelIndexList &indexs)
230{
231 QMenu *menu = new QMenu();
232 bool hasDir = false;
233 bool selOne = indexs.size() == 0;
234 for (auto index: indexs) {
235 if (d->model->isDir(index))
236 hasDir = true;
237 }
238
239 QAction *openAction = new QAction(QAction::tr("Open"));
240 QObject::connect(openAction, &QAction::triggered, this, &TreeView::selOpen);
241 menu->addAction(openAction);
242 if (hasDir) {
243 openAction->setEnabled(false);
244 }
245
246 if (selOne || hasDir) {
247 QAction *newFolderAction = new QAction(QAction::tr("New Folder"));
248 QAction *newDocumentAction = new QAction(QAction::tr("New Document"));
249
250 QObject::connect(newFolderAction, &QAction::triggered, this, &TreeView::selNewFolder);
251 QObject::connect(newDocumentAction, &QAction::triggered, this, &TreeView::selNewDocument);
252 menu->addSeparator();
253 menu->addAction(newFolderAction);
254 menu->addAction(newDocumentAction);
255 }
256
257 QAction *moveToTrashAction = new QAction(QAction::tr("Move To Trash"));
258 QAction *removeAction = new QAction(QAction::tr("Remove"));
259 QObject::connect(moveToTrashAction, &QAction::triggered, this, &TreeView::selMoveToTrash);
260 QObject::connect(removeAction, &QAction::triggered, this, &TreeView::selRemove);
261
262 menu->addSeparator();
263 menu->addAction(moveToTrashAction);
264 menu->addAction(removeAction);
265 return menu;
266}
267
268QMenu *TreeView::createEmptyMenu()
269{
270 QMenu *menu = new QMenu();
271 QAction *recoverFromTrashAction = new QAction(QAction::tr("Recover From Trash"));
272 QObject::connect(recoverFromTrashAction, &QAction::triggered,
273 this, &TreeView::recoverFromTrash);
274 menu->addAction(recoverFromTrashAction);
275 return menu;
276}
277