File indexing completed on 2024-04-28 05:49:04

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 #include "branchdeletedialog.h"
0006 
0007 #include "git/gitutils.h"
0008 
0009 #include <QDialogButtonBox>
0010 #include <QFont>
0011 #include <QHeaderView>
0012 #include <QMouseEvent>
0013 #include <QPainter>
0014 #include <QPushButton>
0015 #include <QVBoxLayout>
0016 
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 #include <ktexteditor/editor.h>
0020 #include <ktexteditor_version.h>
0021 #include <kwidgetsaddons_version.h>
0022 
0023 class CheckableHeaderView : public QHeaderView
0024 {
0025     Q_OBJECT
0026 public:
0027     explicit CheckableHeaderView(Qt::Orientation orientation, QWidget *parent = nullptr)
0028         : QHeaderView(orientation, parent)
0029     {
0030     }
0031 
0032 protected:
0033     void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
0034     {
0035         const int w = style()->pixelMetric(QStyle::PM_IndicatorWidth);
0036         const int h = style()->pixelMetric(QStyle::PM_IndicatorHeight);
0037         const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin) * 2;
0038 
0039         QStyleOptionHeader optHeader;
0040         initStyleOption(&optHeader);
0041         optHeader.rect = rect;
0042         painter->save();
0043         style()->drawControl(QStyle::CE_Header, &optHeader, painter, this);
0044         painter->restore();
0045 
0046         painter->save();
0047         QHeaderView::paintSection(painter, rect.adjusted(margin + w, 0, 0, 0), logicalIndex);
0048         painter->restore();
0049 
0050         if (logicalIndex == 0) {
0051             QStyleOptionButton option;
0052 
0053             option.rect = QRect(0, 0, w, h);
0054             option.rect = QStyle::alignedRect(layoutDirection(), Qt::AlignVCenter, option.rect.size(), rect);
0055             option.rect.moveLeft(rect.left() + margin);
0056             option.state = QStyle::State_Enabled;
0057             if (m_isChecked) {
0058                 option.state |= QStyle::State_On;
0059             } else {
0060                 option.state |= QStyle::State_Off;
0061             }
0062             option.state.setFlag(QStyle::State_MouseOver, m_hovered);
0063             painter->save();
0064             this->style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
0065             painter->restore();
0066         }
0067     }
0068     void mousePressEvent(QMouseEvent *event) override
0069     {
0070         if (!isPosOnCheckBox(event->pos())) {
0071             return;
0072         }
0073 
0074         m_isChecked = !m_isChecked;
0075         viewport()->update();
0076         QMetaObject::invokeMethod(
0077             this,
0078             [this] {
0079                 checkAll(m_isChecked);
0080             },
0081             Qt::QueuedConnection);
0082 
0083         QHeaderView::mousePressEvent(event);
0084     }
0085 
0086     void mouseMoveEvent(QMouseEvent *e) override
0087     {
0088         m_hovered = isPosOnCheckBox(e->pos());
0089         viewport()->update();
0090     }
0091 
0092     void leaveEvent(QEvent *) override
0093     {
0094         m_hovered = false;
0095         viewport()->update();
0096     }
0097 
0098 private:
0099     bool isPosOnCheckBox(QPoint p)
0100     {
0101         const int pos = sectionPosition(0);
0102         const int w = style()->pixelMetric(QStyle::PM_IndicatorWidth);
0103         const int h = style()->pixelMetric(QStyle::PM_IndicatorHeight);
0104         const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin) * 2;
0105         QRect rect = QStyle::alignedRect(layoutDirection(), Qt::AlignVCenter, {w, h}, this->rect());
0106         rect.moveLeft(pos + margin);
0107         return rect.contains(p);
0108     }
0109 
0110     bool m_isChecked = false;
0111     bool m_hovered = false;
0112 
0113 Q_SIGNALS:
0114     void checkAll(bool);
0115 };
0116 
0117 BranchDeleteDialog::BranchDeleteDialog(const QString &dotGitPath, QWidget *parent)
0118     : QDialog(parent)
0119 {
0120     loadBranches(dotGitPath);
0121 
0122     auto l = new QVBoxLayout(this);
0123 
0124     l->addWidget(&m_listView);
0125 
0126     m_model.setHorizontalHeaderLabels({i18n("Branch"), i18n("Last Commit")});
0127 
0128     m_listView.setUniformRowHeights(true);
0129     m_listView.setRootIsDecorated(false);
0130     m_listView.setModel(&m_model);
0131     auto header = new CheckableHeaderView(Qt::Horizontal, this);
0132     connect(header, &CheckableHeaderView::checkAll, this, &BranchDeleteDialog::onCheckAllClicked);
0133     header->setStretchLastSection(true);
0134     m_listView.setHeader(header);
0135 
0136     // setup the buttons
0137     using Btns = QDialogButtonBox::StandardButton;
0138     auto dlgBtns = new QDialogButtonBox(Btns::Cancel, Qt::Horizontal, this);
0139     auto deleteBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete"));
0140     dlgBtns->addButton(deleteBtn, QDialogButtonBox::DestructiveRole);
0141     connect(dlgBtns, &QDialogButtonBox::clicked, this, [this, deleteBtn, dlgBtns](QAbstractButton *btn) {
0142         if (btn == deleteBtn) {
0143             auto count = branchesToDelete().count();
0144             QString ques = i18np("Are you sure you want to delete the selected branch?", "Are you sure you want to delete the selected branches?", count);
0145             auto ret = KMessageBox::questionTwoActions(this, ques, {}, KStandardGuiItem::del(), KStandardGuiItem::cancel(), {}, KMessageBox::Dangerous);
0146             if (ret == KMessageBox::PrimaryAction) {
0147                 accept();
0148             } else {
0149                 // do nothing
0150             }
0151         } else if (dlgBtns->button(QDialogButtonBox::Cancel) == btn) {
0152             reject();
0153         }
0154     });
0155 
0156     connect(dlgBtns, &QDialogButtonBox::accepted, this, &QDialog::accept);
0157     connect(dlgBtns, &QDialogButtonBox::rejected, this, &QDialog::reject);
0158 
0159     l->addWidget(dlgBtns);
0160 
0161     m_listView.resizeColumnToContents(0);
0162     m_listView.resizeColumnToContents(1);
0163 
0164     resize(m_listView.width() * 1.5, m_listView.height() + l->contentsMargins().top() * 2);
0165 }
0166 
0167 void BranchDeleteDialog::loadBranches(const QString &dotGitPath)
0168 {
0169     const auto f = KTextEditor::Editor::instance()->font();
0170     static const auto branchIcon = QIcon::fromTheme(QStringLiteral("vcs-branch"));
0171     const auto branches = GitUtils::getAllLocalBranchesWithLastCommitSubject(dotGitPath);
0172     for (const auto &branch : branches) {
0173         auto branchName = new QStandardItem(branchIcon, branch.name);
0174         auto branchLastCommit = new QStandardItem(branch.lastCommit);
0175         branchName->setFont(f);
0176         branchName->setCheckable(true);
0177         m_model.appendRow({branchName, branchLastCommit});
0178     }
0179 }
0180 
0181 QStringList BranchDeleteDialog::branchesToDelete() const
0182 {
0183     QStringList branches;
0184     int rowCount = m_model.rowCount();
0185     for (int i = 0; i < rowCount; ++i) {
0186         auto item = m_model.item(i);
0187         if (item->checkState() == Qt::Checked) {
0188             branches << item->text();
0189         }
0190     }
0191     return branches;
0192 }
0193 
0194 void BranchDeleteDialog::onCheckAllClicked(bool checked)
0195 {
0196     const int rowCount = m_model.rowCount();
0197     for (int i = 0; i < rowCount; ++i) {
0198         if (auto item = m_model.item(i)) {
0199             item->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
0200         }
0201     }
0202 }
0203 
0204 #include "branchdeletedialog.moc"
0205 #include "moc_branchdeletedialog.cpp"