-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathraventree.cpp
More file actions
325 lines (269 loc) · 11.4 KB
/
raventree.cpp
File metadata and controls
325 lines (269 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
#include "raventree.h"
#include "mainwindow.h"
#include "ravenfile.h"
#include "ravenlhsview.h"
#include "raventreedelegate.h"
#include "raventreemodel.h"
#include "ravenutils.h"
#include <QMenu>
#include <QMessageBox>
#include <QVBoxLayout>
#include <filesystem>
namespace fs = std::filesystem;
RavenTree::RavenTree(QWidget *parent)
: QTreeView{parent}, m_model(new RavenTreeModel(this)), m_contextMenu{new QMenu(this)} {
m_gitManager = qApp->findChild<GitManager *>();
m_lhsView = dynamic_cast<RavenLHSView *>(parent);
// Update tree when Git status changes
connect(m_gitManager, &GitManager::statusChanged, this,
[this](GitManager::status_data sd) { buildTree(m_gitManager->getRepoPath(), std::move(sd)); });
// Context menu
initCustomActions();
// Set model
QTreeView::setModel(m_model);
// Enable mouse tracking so we can stage/unstage items
setMouseTracking(true);
// Use custom delegate to render custom UI elements.
setItemDelegate(new RavenTreeDelegate(this));
// click listener
connect(this, &QAbstractItemView::activated, this, &RavenTree::onFileOpened);
connect(this, &QAbstractItemView::clicked, this, &RavenTree::onFileOpened);
}
RavenTreeModel *RavenTree::model() const { return m_model; }
void RavenTree::initCustomActions() {
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QTreeView::customContextMenuRequested, this, &RavenTree::OnContextMenuRequested);
}
void RavenTree::buildContextMenuForTreeItem(RavenTreeItem *treeItem) {
// Clear previous values
m_contextMenuActionsList.clear();
m_contextMenu->clear();
// Open file/folder in File Manager
if (!treeItem->deleted) {
#if defined(__linux__)
m_openNodeInFMAction = new QAction(QIcon::fromTheme("open-link"), "Open in File Manager", m_contextMenu);
connect(m_openNodeInFMAction, &QAction::triggered, this,
[this, treeItem]() { onOpenNodeInFMRequested(treeItem); });
m_contextMenuActionsList.append(m_openNodeInFMAction);
#endif
}
if (treeItem->initiator == RavenTreeItem::UNCOMMITTED) {
// Stage action
m_stageAction = new QAction(QIcon::fromTheme("list-add"), "Stage", m_contextMenu);
connect(m_stageAction, &QAction::triggered, this, [this, treeItem] { onStageItem(treeItem); });
m_contextMenuActionsList.append(m_stageAction);
// Delete action
m_deleteAction = new QAction(QIcon::fromTheme("delete"), "Delete", m_contextMenu);
connect(m_deleteAction, &QAction::triggered, this, [this, treeItem] { onDeleteRequested(treeItem); });
m_contextMenuActionsList.append(m_deleteAction);
}
if (treeItem->initiator == RavenTreeItem::STAGING) {
// Unstage action
m_unstageAction = new QAction(QIcon::fromTheme("list-remove"), "Unstage", m_contextMenu);
connect(m_unstageAction, &QAction::triggered, this, [this, treeItem] { onUnstageItem(treeItem); });
m_contextMenuActionsList.append(m_unstageAction);
}
m_contextMenu->addActions(m_contextMenuActionsList);
}
void RavenTree::OnContextMenuRequested(const QPoint &pos) {
auto index = indexAt(pos);
if (!index.isValid())
return;
auto item = (RavenTreeItem *)index.internalPointer();
if (!item->heading) {
buildContextMenuForTreeItem(item);
// HACK: menu item y-axis is too high
QPoint *newPos = new QPoint(pos);
newPos->setY(pos.y() + 100);
// Show context menu
m_contextMenu->exec(viewport()->mapFromGlobal(*newPos));
}
}
void RavenTree::buildTree(QString repoPath, GitManager::status_data payload) {
qDebug() << "RavenTree::buildTree called";
QList<GitManager::GitStatusItem> statusItems = payload.statusItems;
RavenTreeItem *rootNode = m_model->getRootNode();
int rowCount = m_model->rowCount();
// Clear previous items
if (rowCount > 0) {
m_model->clear();
}
auto stagingRootNode = m_model->getStagingNode();
auto uncommittedRootNode = m_model->getUncommittedNode();
// Build tree
// If status items count is larger than `MAX_STATUS_FILES_COUNT`,
// inform user that we are rendering `MAX_STATUS_FILES_COUNT` items.
if (statusItems.length() > getMaxStatusFilesCount()) {
this->maxStatusFilesCountReached = true;
// Reset status count to 500
statusItems = statusItems.first(getMaxStatusFilesCount());
// Inform LHSView to show warning banner
emit m_lhsView->signalMaxStatusFileCountReached(true);
} else {
this->maxStatusFilesCountReached = false;
emit m_lhsView->signalMaxStatusFileCountReached(false);
}
for (const auto status : statusItems) {
auto path = status.path;
RavenTreeBuildHelper helper = {.repoPath = repoPath, .currentNode = rootNode, .path = path, .status = status};
if (status.category == RavenTreeItem::BOTH) {
helper.currentNode = m_model->getStagingNode();
_buildTree(helper);
helper.currentNode = m_model->getUncommittedNode();
_buildTree(helper);
} else {
if (status.category == RavenTreeItem::STAGING) {
helper.currentNode = m_model->getStagingNode();
}
if (status.category == RavenTreeItem::UNCOMMITTED) {
helper.currentNode = m_model->getUncommittedNode();
}
_buildTree(helper);
}
}
// Calculate rowCount again
rowCount = m_model->rowCount();
// Append child nodes to rootNode if required
if (rowCount == 0) {
rootNode->children.append(uncommittedRootNode);
rootNode->children.append(stagingRootNode);
}
// emit dataChanged
// FIXME: Figure out the QModelIndex params here.
emit model() -> dataChanged({}, {}, {});
// expand the tree if there are no staged items.
if (stagingRootNode->children.size() == 0) {
expandAll();
} else {
// expand staging node
expandRecursively(m_model->index(1, 0));
}
}
void RavenTree::_buildTree(RavenTreeBuildHelper &helper) {
auto path = helper.path;
auto split = path.split(std::filesystem::path::preferred_separator);
auto currentNode = helper.currentNode;
auto repoPath = helper.repoPath;
auto status = helper.status;
QString currentPathStr = "";
for (auto pathPart : std::as_const(split)) {
// build path from pathPart
fs::path cPath = currentPathStr.toStdString();
// this ensures `/` is applied when expected
cPath /= pathPart.toStdString();
currentPathStr = cPath.c_str();
// Goal: Find parent node
auto it = RavenTreeModel::findNodeByRelPathAndInitiator(currentNode, currentPathStr, currentNode->initiator);
// Node not found in tree, create new node
if (!it) {
// generate absolute path
fs::path repoPathStdFS(repoPath.toStdString());
fs::path objFullPathFSPath(currentPathStr.toStdString());
fs::path fsPath(repoPathStdFS / objFullPathFSPath);
QString objAbsPath = QString::fromStdString(fsPath);
// TODO: migrate to RavenFile class for all this.
RavenFile f;
auto isBinary = f.checkFileIsBinary(objAbsPath);
std::optional<QStringConverter::Encoding> encodingOpt = f.detectEncoding(objAbsPath);
auto *obj = RavenTreeModel::createNode(pathPart, currentPathStr, objAbsPath, status.flag, isBinary, false,
status.deleted, encodingOpt);
obj->initiator = currentNode->initiator;
currentNode->children.append(obj);
currentNode = obj;
} else {
// 2. Parent node found.
currentNode = it;
}
}
}
void RavenTree::onFileOpened(const QModelIndex &index) {
if (!index.isValid())
return;
RavenTreeItem *item = static_cast<RavenTreeItem *>(index.internalPointer());
// We do not show diff items for root nodes.
if (item->heading)
return;
// We do not support non-UTF-8 encoded files.
if (item->encodingOpt.has_value()) {
auto enc = item->encodingOpt.value();
if (enc != QStringConverter::Utf8)
return;
}
qDebug() << "RavenTree::onFileOpened name=" << item->name;
// Get diff item to be shared to editor.
auto diffItem = m_gitManager->diff(item);
// Update category of the item to `initiator` value.
// This fixes issue where editor should not allow editing staged files.
// FIXME: Should we fix it in GitManager?
diffItem.category = item->initiator;
// Inform editor to update view
emit renderDiffItem(diffItem);
}
void RavenTree::mouseReleaseEvent(QMouseEvent *event) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
auto index = indexAt(event->pos());
if (index.isValid()) {
auto treeItem = static_cast<RavenTreeItem *>(index.internalPointer());
auto rowRect = visualRect(index);
auto stageOrUnstageButtonRect = rowRect;
stageOrUnstageButtonRect.setX(rowRect.topRight().x() - 60);
if (stageOrUnstageButtonRect.contains(mouseEvent->pos())) {
auto isStagingItem = treeItem->initiator == RavenTreeItem::STAGING;
if (isStagingItem) {
onUnstageItem(treeItem);
} else {
onStageItem(treeItem);
}
event->ignore();
return;
}
}
QTreeView::mouseReleaseEvent(event);
}
void RavenTree::onStageItem(RavenTreeItem *treeItem) {
qDebug() << "RavenTree::onStageItem called";
int result = m_gitManager->stageItem(treeItem);
if (result != GitManager::GitStageResponseCode::DONE) {
QStringList strings = {};
strings.append("Failed to add file to Staging Area");
strings.append(QString::fromStdString(std::to_string(result)));
QMessageBox errBox(QMessageBox::Icon::Critical, "Error", strings.join(" "), QMessageBox::StandardButton::Ok,
this);
errBox.setModal(true);
errBox.exec();
return;
}
// refresh UI
m_gitManager->statusAsync();
}
void RavenTree::onUnstageItem(RavenTreeItem *treeItem) {
qDebug() << "RavenTree::onUnstageItem called";
int result = m_gitManager->unstageItem(treeItem);
if (result != GitManager::GitStageResponseCode::DONE) {
QStringList strings = {};
strings.append("Failed to remove file to Staging Area");
strings.append(QString::fromStdString(std::to_string(result)));
QMessageBox errBox(QMessageBox::Icon::Critical, "Error", strings.join(" "), QMessageBox::StandardButton::Ok,
this);
errBox.setModal(true);
errBox.exec();
return;
}
// refresh UI
m_gitManager->statusAsync();
}
void RavenTree::onDeleteRequested(RavenTreeItem *treeItem) {
qDebug() << "RavenTree::onDeleteRequested called";
QMessageBox confirmDelete(QMessageBox::Question, "Delete Confirmation", "Are you sure?",
QMessageBox::Yes | QMessageBox::No);
confirmDelete.exec();
if (confirmDelete.result() == QMessageBox::Yes) {
// FIXME: notify user if file deletion failed
RavenUtils::deleteFile(treeItem->absolutePath);
m_gitManager->statusAsync();
}
}
void RavenTree::onOpenNodeInFMRequested(RavenTreeItem *treeItem) {
qDebug() << "RavenTree::onOpenNodeInFMRequested called";
RavenUtils::openNodeInFM(treeItem->absolutePath);
}