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"