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"