File indexing completed on 2024-05-05 04:39:18
0001 /* 0002 SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "checkswidget.h" 0008 #include "ui_checkswidget.h" 0009 0010 #include "checksdb.h" 0011 #include "debug.h" 0012 0013 #include <KLocalizedString> 0014 0015 #include <QMenu> 0016 0017 namespace Clazy 0018 { 0019 0020 enum DataRole { 0021 CheckRole = Qt::UserRole + 1, 0022 DescriptionRole = Qt::UserRole + 2 0023 }; 0024 0025 enum ItemType { 0026 LevelType, 0027 CheckType 0028 }; 0029 0030 ChecksWidget::ChecksWidget(QWidget* parent) 0031 : QWidget(parent) 0032 , m_ui(new Ui::ChecksWidget) 0033 { 0034 m_ui->setupUi(this); 0035 0036 m_ui->filterEdit->addTreeWidget(m_ui->checksTree); 0037 m_ui->filterEdit->setPlaceholderText(i18nc("@info:placeholder", "Search checks...")); 0038 connect(m_ui->filterEdit, &KTreeWidgetSearchLine::searchUpdated, this, &ChecksWidget::searchUpdated); 0039 } 0040 0041 void ChecksWidget::setChecksDb(const QSharedPointer<const ChecksDB>& db) 0042 { 0043 auto resetMenu = new QMenu(this); 0044 m_ui->resetButton->setMenu(resetMenu); 0045 0046 for (auto level : db->levels()) { 0047 auto levelItem = new QTreeWidgetItem(m_ui->checksTree, { level->displayName }, LevelType); 0048 levelItem->setData(0, CheckRole, level->name); 0049 levelItem->setData(0, DescriptionRole, level->description); 0050 levelItem->setCheckState(0, Qt::Unchecked); 0051 0052 m_items[level->name] = levelItem; 0053 0054 auto levelAction = resetMenu->addAction(level->displayName); 0055 connect(levelAction, &QAction::triggered, this, [this, level, levelItem]() { 0056 { 0057 // Block QLineEdit::textChanged() signal, which is used by KTreeWidgetSearchLine to 0058 // start delayed search. 0059 QSignalBlocker blocker(m_ui->filterEdit); 0060 m_ui->filterEdit->clear(); 0061 } 0062 m_ui->filterEdit->updateSearch(); 0063 0064 setChecks(level->name); 0065 m_ui->checksTree->setCurrentItem(levelItem); 0066 }); 0067 0068 for (auto check : qAsConst(level->checks)) { 0069 auto checkItem = new QTreeWidgetItem(levelItem, { check->name }, CheckType); 0070 checkItem->setData(0, CheckRole, check->name); 0071 checkItem->setData(0, DescriptionRole, check->description); 0072 checkItem->setCheckState(0, Qt::Unchecked); 0073 0074 m_items[check->name] = checkItem; 0075 } 0076 } 0077 0078 connect(m_ui->checksTree, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* item) { 0079 setState(item, item->checkState(0)); 0080 updateChecks(); 0081 }); 0082 0083 connect(m_ui->checksTree, &QTreeWidget::currentItemChanged, this, [this, db](QTreeWidgetItem* current) { 0084 if (current) { 0085 m_ui->descriptionView->setText(current->data(0, DescriptionRole).toString()); 0086 } else { 0087 m_ui->descriptionView->clear(); 0088 } 0089 }); 0090 } 0091 0092 ChecksWidget::~ChecksWidget() = default; 0093 0094 QString ChecksWidget::checks() const 0095 { 0096 return m_checks; 0097 } 0098 0099 void ChecksWidget::setChecks(const QString& checks) 0100 { 0101 if (m_checks == checks) { 0102 return; 0103 } 0104 0105 // Clear all selections 0106 for (int i = 0 ; i < m_ui->checksTree->topLevelItemCount(); ++i) { 0107 setState(m_ui->checksTree->topLevelItem(i), Qt::Unchecked); 0108 } 0109 0110 const auto checksList = checks.splitRef(QLatin1Char(','), Qt::SkipEmptyParts); 0111 for (auto& rawCheckName : checksList) { 0112 QString checkName = rawCheckName.trimmed().toString(); 0113 if (checkName == QLatin1String("manual")) { 0114 continue; 0115 } 0116 0117 auto state = Qt::Checked; 0118 if (checkName.startsWith(QLatin1String("no-"))) { 0119 checkName.remove(0, 3); 0120 state = Qt::Unchecked; 0121 } 0122 0123 if (auto checkItem = m_items.value(checkName, nullptr)) { 0124 setState(checkItem, state); 0125 } 0126 } 0127 0128 updateChecks(); 0129 m_ui->checksTree->setCurrentItem(nullptr); 0130 } 0131 0132 QStringList levelChecks( 0133 const QTreeWidget* checksTree, 0134 const QString& levelName, 0135 const QList<const QTreeWidgetItem*>& levelItems) 0136 { 0137 QStringList checksList; 0138 if (!levelName.isEmpty()) { 0139 checksList += levelName; 0140 } 0141 0142 for (int i = 0; i < checksTree->topLevelItemCount(); ++i) { 0143 const auto levelItem = checksTree->topLevelItem(i); 0144 const bool insideLevel = levelItems.contains(levelItem); 0145 0146 for (int j = 0; j < levelItem->childCount(); ++j) { 0147 auto checkItem = levelItem->child(j); 0148 auto checkName = checkItem->data(0, CheckRole).toString(); 0149 0150 if (insideLevel) { 0151 if (checkItem->checkState(0) == Qt::Unchecked) { 0152 checksList += QStringLiteral("no-%1").arg(checkName); 0153 } 0154 } else { 0155 if (checkItem->checkState(0) == Qt::Checked) { 0156 checksList += checkName; 0157 } 0158 } 0159 } 0160 } 0161 0162 return checksList; 0163 } 0164 0165 void ChecksWidget::updateChecks() 0166 { 0167 QStringList checksList; 0168 QList<const QTreeWidgetItem*> levelItems; 0169 0170 // Here we try to find "best" (shortest) checks representation. To do this we build checks list 0171 // for every level and test it's size. 0172 for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { 0173 auto levelItem = m_ui->checksTree->topLevelItem(i); 0174 auto levelName = levelItem->data(0, CheckRole).toString(); 0175 0176 if (levelName == QLatin1String("manual")) { 0177 // Manual level is "fake level" so we clear the name and will store only 0178 // selected checks. 0179 levelItems.clear(); 0180 levelName.clear(); 0181 } else { 0182 levelItems += levelItem; 0183 } 0184 0185 auto levelList = levelChecks(m_ui->checksTree, levelName, levelItems); 0186 if (checksList.isEmpty() || checksList.size() > levelList.size()) { 0187 checksList = levelList; 0188 } 0189 } 0190 0191 m_ui->messageLabel->setVisible(checksList.isEmpty()); 0192 0193 auto checks = checksList.join(QLatin1Char(',')); 0194 if (m_checks != checks) { 0195 m_checks = checks; 0196 emit checksChanged(m_checks); 0197 } 0198 } 0199 0200 void ChecksWidget::setState(QTreeWidgetItem* item, Qt::CheckState state, bool force) 0201 { 0202 Q_ASSERT(item); 0203 0204 QSignalBlocker blocker(m_ui->checksTree); 0205 0206 if (item->type() == LevelType) { 0207 if (state == Qt::Checked) { 0208 // When we enable some non-manual level item, we should also try to enable all 0209 // upper level items. We enable upper item only when it's state is Qt::Unchecked. 0210 // If the state is Qt::PartiallyChecked we assume that it was configured earlier and 0211 // we should skip the item to keep user's checks selection. 0212 const int index = m_ui->checksTree->indexOfTopLevelItem(item); 0213 if (index > 0 && index < (m_ui->checksTree->topLevelItemCount() - 1)) { 0214 setState(m_ui->checksTree->topLevelItem(index - 1), state, false); 0215 } 0216 0217 if (item->checkState(0) != Qt::Unchecked && !force) { 0218 return; 0219 } 0220 } 0221 0222 item->setCheckState(0, state); 0223 if (state != Qt::PartiallyChecked) { 0224 for (int i = 0; i < item->childCount(); ++i) { 0225 item->child(i)->setCheckState(0, state); 0226 } 0227 } 0228 return; 0229 } 0230 0231 item->setCheckState(0, state); 0232 0233 auto levelItem = item->parent(); 0234 Q_ASSERT(levelItem); 0235 0236 const int childCount = levelItem->childCount(); 0237 int checkedCount = 0; 0238 0239 for (int i = 0; i < childCount; ++i) { 0240 if (levelItem->child(i)->checkState(0) == Qt::Checked) { 0241 ++checkedCount; 0242 } 0243 } 0244 0245 if (checkedCount == 0) { 0246 setState(levelItem, Qt::Unchecked); 0247 } else if (checkedCount == childCount) { 0248 setState(levelItem, Qt::Checked); 0249 } else { 0250 setState(levelItem, Qt::PartiallyChecked); 0251 } 0252 } 0253 0254 void ChecksWidget::searchUpdated(const QString& searchString) 0255 { 0256 if (searchString.isEmpty()) { 0257 m_ui->checksTree->collapseAll(); 0258 m_ui->checksTree->setCurrentItem(nullptr); 0259 return; 0260 } 0261 0262 m_ui->checksTree->expandAll(); 0263 0264 QTreeWidgetItem* firstVisibleLevel = nullptr; 0265 for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { 0266 auto levelItem = m_ui->checksTree->topLevelItem(i); 0267 if (levelItem->isHidden()) { 0268 continue; 0269 } 0270 0271 if (!firstVisibleLevel) { 0272 firstVisibleLevel = levelItem; 0273 } 0274 0275 for (int j = 0; j < levelItem->childCount(); ++j) { 0276 auto checkItem = levelItem->child(j); 0277 if (!checkItem->isHidden()) { 0278 m_ui->checksTree->setCurrentItem(checkItem); 0279 return; 0280 } 0281 } 0282 } 0283 0284 m_ui->checksTree->setCurrentItem(firstVisibleLevel); 0285 } 0286 0287 void ChecksWidget::setEditable(bool editable) 0288 { 0289 if (m_isEditable == editable) { 0290 return; 0291 } 0292 0293 m_isEditable = editable; 0294 0295 m_ui->resetButton->setEnabled(editable); 0296 for (auto* item : qAsConst(m_items)) { 0297 auto flags = item->flags(); 0298 flags.setFlag(Qt::ItemIsUserCheckable, m_isEditable); 0299 item->setFlags(flags); 0300 } 0301 } 0302 0303 } 0304 0305 #include "moc_checkswidget.cpp"