1 | #include "BlameWidget.h" |
2 | |
3 | #include <BranchesViewDelegate.h> |
4 | #include <CommitHistoryColumns.h> |
5 | #include <CommitHistoryModel.h> |
6 | #include <CommitHistoryView.h> |
7 | #include <CommitInfo.h> |
8 | #include <FileBlameWidget.h> |
9 | #include <GitHistory.h> |
10 | #include <RepositoryViewDelegate.h> |
11 | |
12 | #include <QApplication> |
13 | #include <QClipboard> |
14 | #include <QFileSystemModel> |
15 | #include <QGridLayout> |
16 | #include <QHeaderView> |
17 | #include <QMenu> |
18 | #include <QTabWidget> |
19 | #include <QTreeView> |
20 | |
21 | BlameWidget::BlameWidget(const QSharedPointer<GitCache> &cache, const QSharedPointer<GitBase> &git, |
22 | const QSharedPointer<GitQlientSettings> &settings, QWidget *parent) |
23 | : QFrame(parent) |
24 | , mCache(cache) |
25 | , mGit(git) |
26 | , mSettings(settings) |
27 | , fileSystemModel(new QFileSystemModel()) |
28 | , mRepoModel(new CommitHistoryModel(mCache, mGit, nullptr)) |
29 | , mRepoView(new CommitHistoryView(mCache, mGit, mSettings, nullptr)) |
30 | , fileSystemView(new QTreeView()) |
31 | , mTabWidget(new QTabWidget()) |
32 | { |
33 | mTabWidget->setObjectName("HistoryTab" ); |
34 | mRepoView->setObjectName("blameGraphView" ); |
35 | mRepoView->setModel(mRepoModel); |
36 | mRepoView->header()->setSectionHidden(static_cast<int>(CommitHistoryColumns::Graph), true); |
37 | mRepoView->header()->setSectionHidden(static_cast<int>(CommitHistoryColumns::Date), true); |
38 | mRepoView->header()->setSectionHidden(static_cast<int>(CommitHistoryColumns::Author), true); |
39 | mRepoView->setItemDelegate(mItemDelegate = new RepositoryViewDelegate(cache, mGit, nullptr, mRepoView)); |
40 | mRepoView->setEnabled(true); |
41 | mRepoView->setMaximumWidth(450); |
42 | mRepoView->setSelectionBehavior(QAbstractItemView::SelectRows); |
43 | mRepoView->setSelectionMode(QAbstractItemView::SingleSelection); |
44 | mRepoView->setContextMenuPolicy(Qt::CustomContextMenu); |
45 | mRepoView->header()->setContextMenuPolicy(Qt::NoContextMenu); |
46 | mRepoView->activateFilter(true); |
47 | mRepoView->filterBySha({}); |
48 | connect(mRepoView, &CommitHistoryView::customContextMenuRequested, this, &BlameWidget::showRepoViewMenu); |
49 | connect(mRepoView, &CommitHistoryView::clicked, this, &BlameWidget::reloadBlame); |
50 | connect(mRepoView, &CommitHistoryView::doubleClicked, this, &BlameWidget::openDiff); |
51 | |
52 | fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); |
53 | |
54 | fileSystemView->setModel(fileSystemModel); |
55 | fileSystemView->setMaximumWidth(450); |
56 | fileSystemView->header()->setSectionHidden(1, true); |
57 | fileSystemView->header()->setSectionHidden(2, true); |
58 | fileSystemView->header()->setSectionHidden(3, true); |
59 | fileSystemView->setContextMenuPolicy(Qt::CustomContextMenu); |
60 | connect(fileSystemView, &QTreeView::clicked, this, &BlameWidget::showFileHistoryByIndex); |
61 | |
62 | const auto historyBlameLayout = new QGridLayout(this); |
63 | historyBlameLayout->setContentsMargins(QMargins()); |
64 | historyBlameLayout->addWidget(mRepoView, 0, 0); |
65 | historyBlameLayout->addWidget(fileSystemView, 1, 0); |
66 | historyBlameLayout->addWidget(mTabWidget, 0, 1, 2, 1); |
67 | |
68 | mTabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); |
69 | |
70 | connect(mTabWidget, &QTabWidget::tabCloseRequested, mTabWidget, [this](int index) { |
71 | if (index == mLastTabIndex) |
72 | { |
73 | fileSystemView->clearSelection(); |
74 | mRepoView->blockSignals(true); |
75 | mRepoView->filterBySha({}); |
76 | mRepoView->blockSignals(false); |
77 | } |
78 | |
79 | auto widget = qobject_cast<FileBlameWidget *>(mTabWidget->widget(index)); |
80 | mTabWidget->removeTab(index); |
81 | const auto key = mTabsMap.key(widget); |
82 | mTabsMap.remove(key); |
83 | |
84 | delete widget; |
85 | }); |
86 | connect(mTabWidget, &QTabWidget::currentChanged, this, &BlameWidget::reloadHistory); |
87 | |
88 | setAttribute(Qt::WA_DeleteOnClose); |
89 | } |
90 | |
91 | BlameWidget::~BlameWidget() |
92 | { |
93 | delete mRepoModel; |
94 | delete mItemDelegate; |
95 | delete fileSystemModel; |
96 | } |
97 | |
98 | void BlameWidget::init(const QString &workingDirectory) |
99 | { |
100 | mWorkingDirectory = workingDirectory; |
101 | fileSystemModel->setRootPath(workingDirectory); |
102 | fileSystemView->setRootIndex(fileSystemModel->index(workingDirectory)); |
103 | } |
104 | |
105 | void BlameWidget::showFileHistory(const QString &filePath) |
106 | { |
107 | if (!mTabsMap.contains(filePath)) |
108 | { |
109 | QScopedPointer<GitHistory> git(new GitHistory(mGit)); |
110 | auto ret = git->history(filePath); |
111 | |
112 | if (ret.success) |
113 | { |
114 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) |
115 | auto shaHistory = ret.output.split("\n" , Qt::SkipEmptyParts); |
116 | #else |
117 | auto shaHistory = ret.output.split("\n" , QString::SkipEmptyParts); |
118 | #endif |
119 | for (auto i = 0; i < shaHistory.size();) |
120 | { |
121 | if (shaHistory.at(i).startsWith("gpg:" )) |
122 | { |
123 | shaHistory.takeAt(i); |
124 | |
125 | if (shaHistory.size() <= i) |
126 | break; |
127 | } |
128 | else |
129 | ++i; |
130 | } |
131 | |
132 | mRepoView->blockSignals(true); |
133 | mRepoView->filterBySha(shaHistory); |
134 | mRepoView->blockSignals(false); |
135 | |
136 | const auto previousSha = shaHistory.count() > 1 ? shaHistory.at(1) : QString(tr("No info" )); |
137 | const auto fileBlameWidget = new FileBlameWidget(mCache, mGit); |
138 | |
139 | fileBlameWidget->setup(filePath, shaHistory.constFirst(), previousSha); |
140 | connect(fileBlameWidget, &FileBlameWidget::signalCommitSelected, mRepoView, &CommitHistoryView::focusOnCommit); |
141 | |
142 | const auto index = mTabWidget->addTab(fileBlameWidget, filePath.split("/" ).last()); |
143 | mTabWidget->setTabsClosable(true); |
144 | mTabWidget->blockSignals(true); |
145 | mTabWidget->setCurrentIndex(index); |
146 | mTabWidget->blockSignals(false); |
147 | |
148 | mLastTabIndex = index; |
149 | mTabsMap.insert(filePath, fileBlameWidget); |
150 | } |
151 | } |
152 | else |
153 | mTabWidget->setCurrentWidget(mTabsMap.value(filePath)); |
154 | } |
155 | |
156 | void BlameWidget::onNewRevisions(int totalCommits) |
157 | { |
158 | mRepoModel->onNewRevisions(totalCommits); |
159 | } |
160 | |
161 | void BlameWidget::reloadBlame(const QModelIndex &index) |
162 | { |
163 | mSelectedRow = index.row(); |
164 | const auto blameWidget = qobject_cast<FileBlameWidget *>(mTabWidget->currentWidget()); |
165 | |
166 | if (blameWidget) |
167 | { |
168 | const auto sha |
169 | = mRepoView->model()->index(index.row(), static_cast<int>(CommitHistoryColumns::Sha)).data().toString(); |
170 | const auto previousSha |
171 | = mRepoView->model()->index(index.row() + 1, static_cast<int>(CommitHistoryColumns::Sha)).data().toString(); |
172 | blameWidget->reload(sha, previousSha); |
173 | } |
174 | } |
175 | |
176 | void BlameWidget::reloadHistory(int tabIndex) |
177 | { |
178 | if (tabIndex >= 0) |
179 | { |
180 | mLastTabIndex = tabIndex; |
181 | |
182 | const auto blameWidget = qobject_cast<FileBlameWidget *>(mTabWidget->widget(tabIndex)); |
183 | const auto sha = blameWidget->getCurrentSha(); |
184 | const auto file = blameWidget->getCurrentFile(); |
185 | |
186 | QScopedPointer<GitHistory> git(new GitHistory(mGit)); |
187 | const auto ret = git->history(file); |
188 | |
189 | if (ret.success) |
190 | { |
191 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) |
192 | auto shaHistory = ret.output.split("\n" , Qt::SkipEmptyParts); |
193 | #else |
194 | auto shaHistory = ret.output.split("\n" , QString::SkipEmptyParts); |
195 | #endif |
196 | for (auto i = 0; i < shaHistory.size();) |
197 | { |
198 | if (shaHistory.at(i).startsWith("gpg:" )) |
199 | { |
200 | shaHistory.takeAt(i); |
201 | |
202 | if (shaHistory.size() <= i) |
203 | break; |
204 | } |
205 | else |
206 | ++i; |
207 | } |
208 | |
209 | mRepoView->blockSignals(true); |
210 | mRepoView->filterBySha(shaHistory); |
211 | |
212 | const auto repoModel = mRepoView->model(); |
213 | const auto totalRows = repoModel->rowCount(); |
214 | for (auto i = 0; i < totalRows; ++i) |
215 | { |
216 | const auto index = mRepoView->model()->index(i, static_cast<int>(CommitHistoryColumns::Sha)); |
217 | |
218 | if (index.data().toString().startsWith(sha)) |
219 | { |
220 | mRepoView->setCurrentIndex(index); |
221 | mRepoView->selectionModel()->select(index, |
222 | QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); |
223 | } |
224 | } |
225 | |
226 | mRepoView->blockSignals(false); |
227 | } |
228 | } |
229 | } |
230 | |
231 | void BlameWidget::showFileHistoryByIndex(const QModelIndex &index) |
232 | { |
233 | auto item = fileSystemModel->fileInfo(index); |
234 | |
235 | if (item.isFile()) |
236 | showFileHistory(item.filePath()); |
237 | } |
238 | |
239 | void BlameWidget::(const QPoint &pos) |
240 | { |
241 | const auto shaColumnIndex = static_cast<int>(CommitHistoryColumns::Sha); |
242 | const auto modelIndex = mRepoView->model()->index(mSelectedRow, shaColumnIndex); |
243 | |
244 | reloadBlame(modelIndex); |
245 | |
246 | const auto sha = modelIndex.data().toString(); |
247 | const auto previousSha = mRepoView->model()->index(mSelectedRow + 1, shaColumnIndex).data().toString(); |
248 | const auto = new QMenu(this); |
249 | const auto copyShaAction = menu->addAction(tr("Copy SHA" )); |
250 | connect(copyShaAction, &QAction::triggered, this, [sha]() { QApplication::clipboard()->setText(sha); }); |
251 | |
252 | const auto fileDiff = menu->addAction(tr("Show file diff" )); |
253 | connect(fileDiff, &QAction::triggered, this, [this, sha, previousSha]() { |
254 | const auto currentFile = qobject_cast<FileBlameWidget *>(mTabWidget->currentWidget())->getCurrentFile(); |
255 | emit showFileDiff(sha, previousSha, currentFile, false); |
256 | }); |
257 | |
258 | const auto commitDiff = menu->addAction(tr("Show commit diff" )); |
259 | connect(commitDiff, &QAction::triggered, this, [this, sha, previousSha]() { |
260 | emit signalOpenDiff({ previousSha, sha }); |
261 | }); |
262 | |
263 | menu->exec(mRepoView->viewport()->mapToGlobal(pos)); |
264 | } |
265 | |
266 | void BlameWidget::openDiff(const QModelIndex &index) |
267 | { |
268 | const auto sha |
269 | = mRepoView->model()->index(index.row(), static_cast<int>(CommitHistoryColumns::Sha)).data().toString(); |
270 | const auto previousSha |
271 | = mRepoView->model()->index(index.row() + 1, static_cast<int>(CommitHistoryColumns::Sha)).data().toString(); |
272 | |
273 | emit signalOpenDiff({ previousSha, sha }); |
274 | } |
275 | |