| 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 | |
| 26 | using namespace QLogger; |
| 27 | |
| 28 | Controls::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 = 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 | |
| 204 | Controls::~Controls() |
| 205 | { |
| 206 | delete mBtnGroup; |
| 207 | } |
| 208 | |
| 209 | void Controls::toggleButton(ControlsMainViews view) |
| 210 | { |
| 211 | mBtnGroup->button(static_cast<int>(view))->setChecked(true); |
| 212 | } |
| 213 | |
| 214 | void 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 | |
| 236 | void 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 | |
| 270 | void 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 | |
| 284 | void Controls::activateMergeWarning() |
| 285 | { |
| 286 | mMergeWarning->setVisible(true); |
| 287 | } |
| 288 | |
| 289 | void Controls::disableMergeWarning() |
| 290 | { |
| 291 | mMergeWarning->setVisible(false); |
| 292 | } |
| 293 | |
| 294 | void Controls::disableDiff() |
| 295 | { |
| 296 | mDiff->setDisabled(true); |
| 297 | } |
| 298 | |
| 299 | void Controls::enableDiff() |
| 300 | { |
| 301 | mDiff->setEnabled(true); |
| 302 | } |
| 303 | |
| 304 | ControlsMainViews Controls::getCurrentSelectedButton() const |
| 305 | { |
| 306 | return mBlame->isChecked() ? ControlsMainViews::Blame : ControlsMainViews::History; |
| 307 | } |
| 308 | |
| 309 | void 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 | |
| 357 | void 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 | |
| 368 | void 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 | |
| 412 | void 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 | |
| 422 | bool Controls::eventFilter(QObject *obj, QEvent *event) |
| 423 | { |
| 424 | if (const auto = 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 | |