1#include "Controls.h"
2
3#include <BranchDlg.h>
4#include <GitBase.h>
5#include <GitCache.h>
6#include <GitConfig.h>
7#include <GitQlientSettings.h>
8#include <GitQlientStyles.h>
9#include <GitQlientUpdater.h>
10#include <GitRemote.h>
11#include <GitStashes.h>
12#include <GitTags.h>
13#include <PomodoroButton.h>
14#include <QLogger.h>
15
16#include <QApplication>
17#include <QButtonGroup>
18#include <QHBoxLayout>
19#include <QMenu>
20#include <QMessageBox>
21#include <QProgressBar>
22#include <QPushButton>
23#include <QTimer>
24#include <QToolButton>
25
26using namespace QLogger;
27
28Controls::Controls(const QSharedPointer<GitCache> &cache, const QSharedPointer<GitBase> &git, QWidget *parent)
29 : QFrame(parent)
30 , mCache(cache)
31 , mGit(git)
32 , mGitTags(new GitTags(mGit, mCache))
33 , mHistory(new QToolButton())
34 , mDiff(new QToolButton())
35 , mBlame(new QToolButton())
36 , mPullBtn(new QToolButton())
37 , mPullOptions(new QToolButton())
38 , mPushBtn(new QToolButton())
39 , mRefreshBtn(new QToolButton())
40 , mConfigBtn(new QToolButton())
41 , mGitPlatform(new QToolButton())
42 , mBuildSystem(new QToolButton())
43 , mPomodoro(new PomodoroButton(mGit))
44 , mVersionCheck(new QToolButton())
45 , mMergeWarning(new QPushButton(tr("WARNING: There is a merge pending to be committed! Click here to solve it.")))
46 , mUpdater(new GitQlientUpdater(this))
47 , mBtnGroup(new QButtonGroup())
48{
49 setAttribute(Qt::WA_DeleteOnClose);
50
51 connect(mUpdater, &GitQlientUpdater::newVersionAvailable, this, [this]() { mVersionCheck->setVisible(true); });
52
53 mHistory->setCheckable(true);
54 mHistory->setIcon(QIcon(":/icons/git_orange"));
55 mHistory->setIconSize(QSize(22, 22));
56 mHistory->setToolTip(tr("View"));
57 mHistory->setToolButtonStyle(Qt::ToolButtonIconOnly);
58 mBtnGroup->addButton(mHistory, static_cast<int>(ControlsMainViews::History));
59
60 mDiff->setCheckable(true);
61 mDiff->setIcon(QIcon(":/icons/diff"));
62 mDiff->setIconSize(QSize(22, 22));
63 mDiff->setToolTip(tr("Diff"));
64 mDiff->setToolButtonStyle(Qt::ToolButtonIconOnly);
65 mDiff->setEnabled(false);
66 mBtnGroup->addButton(mDiff, static_cast<int>(ControlsMainViews::Diff));
67
68 mBlame->setCheckable(true);
69 mBlame->setIcon(QIcon(":/icons/blame"));
70 mBlame->setIconSize(QSize(22, 22));
71 mBlame->setToolTip(tr("Blame"));
72 mBlame->setToolButtonStyle(Qt::ToolButtonIconOnly);
73 mBtnGroup->addButton(mBlame, static_cast<int>(ControlsMainViews::Blame));
74
75 const auto menu = new QMenu(mPullOptions);
76 menu->installEventFilter(this);
77
78 auto action = menu->addAction(tr("Fetch all"));
79 connect(action, &QAction::triggered, this, &Controls::fetchAll);
80
81 action = menu->addAction(tr("Prune"));
82 connect(action, &QAction::triggered, this, &Controls::pruneBranches);
83 menu->addSeparator();
84
85 mPullBtn->setIconSize(QSize(22, 22));
86 mPullBtn->setToolTip(tr("Pull"));
87 mPullBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
88 mPullBtn->setPopupMode(QToolButton::InstantPopup);
89 mPullBtn->setIcon(QIcon(":/icons/git_pull"));
90 mPullBtn->setObjectName("ToolButtonAboveMenu");
91
92 mPullOptions->setMenu(menu);
93 mPullOptions->setIcon(QIcon(":/icons/arrow_down"));
94 mPullOptions->setIconSize(QSize(22, 22));
95 mPullOptions->setToolButtonStyle(Qt::ToolButtonIconOnly);
96 mPullOptions->setPopupMode(QToolButton::InstantPopup);
97 mPullOptions->setToolTip("Remote actions");
98 mPullOptions->setObjectName("ToolButtonWithMenu");
99
100 const auto pullLayout = new QVBoxLayout();
101 pullLayout->setContentsMargins(QMargins());
102 pullLayout->setSpacing(0);
103 pullLayout->addWidget(mPullBtn);
104 pullLayout->addWidget(mPullOptions);
105
106 mPushBtn->setIcon(QIcon(":/icons/git_push"));
107 mPushBtn->setIconSize(QSize(22, 22));
108 mPushBtn->setToolTip(tr("Push"));
109 mPushBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
110
111 mRefreshBtn->setIcon(QIcon(":/icons/refresh"));
112 mRefreshBtn->setIconSize(QSize(22, 22));
113 mRefreshBtn->setToolTip(tr("Refresh"));
114 mRefreshBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
115
116 mConfigBtn->setCheckable(true);
117 mConfigBtn->setIcon(QIcon(":/icons/config"));
118 mConfigBtn->setIconSize(QSize(22, 22));
119 mConfigBtn->setToolTip(tr("Config"));
120 mConfigBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
121 mBtnGroup->addButton(mConfigBtn, static_cast<int>(ControlsMainViews::Config));
122
123 const auto separator = new QFrame();
124 separator->setObjectName("orangeSeparator");
125 separator->setFixedHeight(20);
126
127 const auto separator2 = new QFrame();
128 separator2->setObjectName("orangeSeparator");
129 separator2->setFixedHeight(20);
130
131 const auto hLayout = new QHBoxLayout();
132 hLayout->setContentsMargins(QMargins());
133 hLayout->addStretch();
134 hLayout->setSpacing(5);
135 hLayout->addWidget(mHistory);
136 hLayout->addWidget(mDiff);
137 hLayout->addWidget(mBlame);
138 hLayout->addWidget(separator);
139 hLayout->addLayout(pullLayout);
140 hLayout->addWidget(mPushBtn);
141 hLayout->addWidget(separator2);
142
143 createGitPlatformButton(hLayout);
144
145 GitQlientSettings settings(mGit->getGitDir());
146 mBuildSystem->setVisible(settings.localValue("BuildSystemEnabled", false).toBool());
147 mBuildSystem->setCheckable(true);
148 mBuildSystem->setIcon(QIcon(":/icons/build_system"));
149 mBuildSystem->setIconSize(QSize(22, 22));
150 mBuildSystem->setToolTip("Jenkins");
151 mBuildSystem->setToolButtonStyle(Qt::ToolButtonIconOnly);
152 mBuildSystem->setPopupMode(QToolButton::InstantPopup);
153 mBtnGroup->addButton(mBuildSystem, static_cast<int>(ControlsMainViews::BuildSystem));
154
155 connect(mBuildSystem, &QToolButton::clicked, this, &Controls::signalGoBuildSystem);
156
157 hLayout->addWidget(mBuildSystem);
158
159 configBuildSystemButton();
160
161 const auto separator3 = new QFrame();
162 separator3->setObjectName("orangeSeparator");
163 separator3->setFixedHeight(20);
164 hLayout->addWidget(separator3);
165
166 mVersionCheck->setIcon(QIcon(":/icons/get_gitqlient"));
167 mVersionCheck->setIconSize(QSize(22, 22));
168 mVersionCheck->setText(tr("New version"));
169 mVersionCheck->setObjectName("longToolButton");
170 mVersionCheck->setToolButtonStyle(Qt::ToolButtonIconOnly);
171 mVersionCheck->setVisible(false);
172
173 mUpdater->checkNewGitQlientVersion();
174
175 hLayout->addWidget(mRefreshBtn);
176 hLayout->addWidget(mConfigBtn);
177 hLayout->addWidget(mPomodoro);
178 hLayout->addWidget(mVersionCheck);
179 hLayout->addStretch();
180
181 mMergeWarning->setObjectName("WarningButton");
182 mMergeWarning->setVisible(false);
183 mBtnGroup->addButton(mMergeWarning, static_cast<int>(ControlsMainViews::Merge));
184
185 const auto vLayout = new QVBoxLayout(this);
186 vLayout->setContentsMargins(0, 5, 0, 0);
187 vLayout->setSpacing(10);
188 vLayout->addLayout(hLayout);
189 vLayout->addWidget(mMergeWarning);
190
191 connect(mHistory, &QToolButton::clicked, this, &Controls::signalGoRepo);
192 connect(mDiff, &QToolButton::clicked, this, &Controls::signalGoDiff);
193 connect(mBlame, &QToolButton::clicked, this, &Controls::signalGoBlame);
194 connect(mPullBtn, &QToolButton::clicked, this, &Controls::pullCurrentBranch);
195 connect(mPushBtn, &QToolButton::clicked, this, &Controls::pushCurrentBranch);
196 connect(mRefreshBtn, &QToolButton::clicked, this, &Controls::requestFullReload);
197 connect(mMergeWarning, &QPushButton::clicked, this, &Controls::signalGoMerge);
198 connect(mVersionCheck, &QToolButton::clicked, mUpdater, &GitQlientUpdater::showInfoMessage);
199 connect(mConfigBtn, &QToolButton::clicked, this, &Controls::goConfig);
200
201 enableButtons(false);
202}
203
204Controls::~Controls()
205{
206 delete mBtnGroup;
207}
208
209void Controls::toggleButton(ControlsMainViews view)
210{
211 mBtnGroup->button(static_cast<int>(view))->setChecked(true);
212}
213
214void Controls::enableButtons(bool enabled)
215{
216 mHistory->setEnabled(enabled);
217 mBlame->setEnabled(enabled);
218 mPullBtn->setEnabled(enabled);
219 mPullOptions->setEnabled(enabled);
220 mPushBtn->setEnabled(enabled);
221 mRefreshBtn->setEnabled(enabled);
222 mGitPlatform->setEnabled(enabled);
223 mConfigBtn->setEnabled(enabled);
224
225 if (enabled)
226 {
227 GitQlientSettings settings(mGit->getGitDir());
228 const auto isConfigured = settings.localValue("BuildSystemEnabled", false).toBool();
229
230 mBuildSystem->setEnabled(isConfigured);
231 }
232 else
233 mBuildSystem->setEnabled(false);
234}
235
236void Controls::pullCurrentBranch()
237{
238 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
239 QScopedPointer<GitRemote> git(new GitRemote(mGit));
240 const auto ret = git->pull();
241 QApplication::restoreOverrideCursor();
242
243 if (ret.success)
244 {
245 if (ret.output.contains("merge conflict", Qt::CaseInsensitive))
246 emit signalPullConflict();
247 else
248 emit requestFullReload();
249 }
250 else
251 {
252 if (ret.output.contains("error: could not apply", Qt::CaseInsensitive)
253 && ret.output.contains("causing a conflict", Qt::CaseInsensitive))
254 {
255 emit signalPullConflict();
256 }
257 else
258 {
259 QMessageBox msgBox(QMessageBox::Critical, tr("Error while pulling"),
260 QString(tr("There were problems during the pull operation. Please, see the detailed "
261 "description for more information.")),
262 QMessageBox::Ok, this);
263 msgBox.setDetailedText(ret.output);
264 msgBox.setStyleSheet(GitQlientStyles::getStyles());
265 msgBox.exec();
266 }
267 }
268}
269
270void Controls::fetchAll()
271{
272 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
273 QScopedPointer<GitRemote> git(new GitRemote(mGit));
274 const auto ret = git->fetch();
275 QApplication::restoreOverrideCursor();
276
277 if (ret)
278 {
279 mGitTags->getRemoteTags();
280 emit requestFullReload();
281 }
282}
283
284void Controls::activateMergeWarning()
285{
286 mMergeWarning->setVisible(true);
287}
288
289void Controls::disableMergeWarning()
290{
291 mMergeWarning->setVisible(false);
292}
293
294void Controls::disableDiff()
295{
296 mDiff->setDisabled(true);
297}
298
299void Controls::enableDiff()
300{
301 mDiff->setEnabled(true);
302}
303
304ControlsMainViews Controls::getCurrentSelectedButton() const
305{
306 return mBlame->isChecked() ? ControlsMainViews::Blame : ControlsMainViews::History;
307}
308
309void Controls::pushCurrentBranch()
310{
311 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
312 QScopedPointer<GitRemote> git(new GitRemote(mGit));
313 const auto ret = git->push();
314 QApplication::restoreOverrideCursor();
315
316 if (ret.output.contains("has no upstream branch"))
317 {
318 const auto currentBranch = mGit->getCurrentBranch();
319 BranchDlg dlg({ currentBranch, BranchDlgMode::PUSH_UPSTREAM, mCache, mGit });
320 const auto dlgRet = dlg.exec();
321
322 if (dlgRet == QDialog::Accepted)
323 emit signalRefreshPRsCache();
324 }
325 else if (ret.success)
326 {
327 const auto currentBranch = mGit->getCurrentBranch();
328 QScopedPointer<GitConfig> git(new GitConfig(mGit));
329 const auto remote = git->getRemoteForBranch(currentBranch);
330
331 if (remote.success)
332 {
333 const auto oldSha = mCache->getShaOfReference(QString("%1/%2").arg(remote.output, currentBranch),
334 References::Type::RemoteBranches);
335 const auto sha = mCache->getShaOfReference(currentBranch, References::Type::LocalBranch);
336 mCache->deleteReference(oldSha, References::Type::RemoteBranches,
337 QString("%1/%2").arg(remote.output, currentBranch));
338 mCache->insertReference(sha, References::Type::RemoteBranches,
339 QString("%1/%2").arg(remote.output, currentBranch));
340 emit mCache->signalCacheUpdated();
341 emit signalRefreshPRsCache();
342 }
343 }
344 else
345 {
346 QMessageBox msgBox(
347 QMessageBox::Critical, tr("Error while pushing"),
348 QString(tr("There were problems during the push operation. Please, see the detailed description "
349 "for more information.")),
350 QMessageBox::Ok, this);
351 msgBox.setDetailedText(ret.output);
352 msgBox.setStyleSheet(GitQlientStyles::getStyles());
353 msgBox.exec();
354 }
355}
356
357void Controls::pruneBranches()
358{
359 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
360 QScopedPointer<GitRemote> git(new GitRemote(mGit));
361 const auto ret = git->prune();
362 QApplication::restoreOverrideCursor();
363
364 if (ret.success)
365 emit requestReferencesReload();
366}
367
368void Controls::createGitPlatformButton(QHBoxLayout *layout)
369{
370 QScopedPointer<GitConfig> gitConfig(new GitConfig(mGit));
371 const auto remoteUrl = gitConfig->getServerHost();
372 QIcon gitPlatformIcon;
373 QString name;
374 QString prName;
375 auto add = false;
376
377 if (remoteUrl.contains("github", Qt::CaseInsensitive))
378 {
379 add = true;
380
381 gitPlatformIcon = QIcon(":/icons/github");
382 name = "GitHub";
383 prName = tr("Pull Request");
384 }
385 else if (remoteUrl.contains("gitlab", Qt::CaseInsensitive))
386 {
387 add = true;
388
389 gitPlatformIcon = QIcon(":/icons/gitlab");
390 name = "GitLab";
391 prName = tr("Merge Request");
392 }
393
394 if (add)
395 {
396 mGitPlatform->setCheckable(true);
397 mGitPlatform->setIcon(gitPlatformIcon);
398 mGitPlatform->setIconSize(QSize(22, 22));
399 mGitPlatform->setToolTip(name);
400 mGitPlatform->setToolButtonStyle(Qt::ToolButtonIconOnly);
401 mGitPlatform->setPopupMode(QToolButton::InstantPopup);
402 mBtnGroup->addButton(mGitPlatform, static_cast<int>(ControlsMainViews::GitServer));
403
404 layout->addWidget(mGitPlatform);
405
406 connect(mGitPlatform, &QToolButton::clicked, this, &Controls::signalGoServer);
407 }
408 else
409 mGitPlatform->setVisible(false);
410}
411
412void Controls::configBuildSystemButton()
413{
414 GitQlientSettings settings(mGit->getGitDir());
415 const auto isConfigured = settings.localValue("BuildSystemEnabled", false).toBool();
416 mBuildSystem->setEnabled(isConfigured);
417
418 if (!isConfigured)
419 emit signalGoRepo();
420}
421
422bool Controls::eventFilter(QObject *obj, QEvent *event)
423{
424 if (const auto menu = qobject_cast<QMenu *>(obj); menu && event->type() == QEvent::Show)
425 {
426 auto localPos = menu->parentWidget()->pos();
427 localPos.setX(localPos.x());
428 auto pos = mapToGlobal(localPos);
429 menu->show();
430 pos.setY(pos.y() + menu->parentWidget()->height());
431 menu->move(pos);
432 return true;
433 }
434
435 return false;
436}
437