diff --git a/plugins/clazy/config/checkswidget.cpp b/plugins/clazy/config/checkswidget.cpp index de8141a4bd..bf41438f07 100644 --- a/plugins/clazy/config/checkswidget.cpp +++ b/plugins/clazy/config/checkswidget.cpp @@ -1,298 +1,300 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "checkswidget.h" #include "ui_checkswidget.h" #include "checksdb.h" #include "debug.h" +#include + #include #include namespace Clazy { enum DataRole { CheckRole = Qt::UserRole + 1, DescriptionRole = Qt::UserRole + 2 }; enum ItemType { LevelType, CheckType }; ChecksWidget::ChecksWidget(QSharedPointer db, QWidget* parent) : QWidget(parent) , m_ui(new Ui::ChecksWidget) { m_ui->setupUi(this); m_ui->filterEdit->addTreeWidget(m_ui->checksTree); m_ui->filterEdit->setPlaceholderText(i18n("Search checks...")); connect(m_ui->filterEdit, &KTreeWidgetSearchLine::searchUpdated, this, &ChecksWidget::searchUpdated); auto resetMenu = new QMenu(this); m_ui->resetButton->setMenu(resetMenu); for (auto level : db->levels()) { auto levelItem = new QTreeWidgetItem(m_ui->checksTree, { level->displayName }, LevelType); levelItem->setData(0, CheckRole, level->name); levelItem->setData(0, DescriptionRole, level->description); levelItem->setCheckState(0, Qt::Unchecked); m_items[level->name] = levelItem; auto levelAction = resetMenu->addAction(level->displayName); connect(levelAction, &QAction::triggered, this, [this, level, levelItem]() { { // Block QLineEdit::textChanged() signal, which is used by KTreeWidgetSearchLine to // start delayed search. QSignalBlocker blocker(m_ui->filterEdit); m_ui->filterEdit->clear(); } m_ui->filterEdit->updateSearch(); setChecks(level->name); m_ui->checksTree->setCurrentItem(levelItem); }); for (auto check : qAsConst(level->checks)) { auto checkItem = new QTreeWidgetItem(levelItem, { check->name }, CheckType); checkItem->setData(0, CheckRole, check->name); checkItem->setData(0, DescriptionRole, check->description); checkItem->setCheckState(0, Qt::Unchecked); m_items[check->name] = checkItem; } } connect(m_ui->checksTree, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* item) { setState(item, item->checkState(0)); updateChecks(); }); connect(m_ui->checksTree, &QTreeWidget::currentItemChanged, this, [this, db](QTreeWidgetItem* current) { if (current) { m_ui->descriptionView->setText(current->data(0, DescriptionRole).toString()); } else { m_ui->descriptionView->clear(); } }); } ChecksWidget::~ChecksWidget() = default; QString ChecksWidget::checks() const { return m_checks; } void ChecksWidget::setChecks(const QString& checks) { if (m_checks == checks) { return; } // Clear all selections for (int i = 0 ; i < m_ui->checksTree->topLevelItemCount(); ++i) { setState(m_ui->checksTree->topLevelItem(i), Qt::Unchecked); } const QStringList checksList = checks.split(',', QString::SkipEmptyParts); for (auto checkName : checksList) { checkName = checkName.trimmed(); if (checkName == QStringLiteral("manual")) { continue; } auto state = Qt::Checked; if (checkName.startsWith(QStringLiteral("no-"))) { checkName = checkName.mid(3); state = Qt::Unchecked; } if (auto checkItem = m_items.value(checkName, nullptr)) { setState(checkItem, state); } } updateChecks(); m_ui->checksTree->setCurrentItem(nullptr); } QStringList levelChecks( const QTreeWidget* checksTree, const QString& levelName, const QList& levelItems) { QStringList checksList; if (!levelName.isEmpty()) { checksList += levelName; } for (int i = 0; i < checksTree->topLevelItemCount(); ++i) { const auto levelItem = checksTree->topLevelItem(i); const bool insideLevel = levelItems.contains(levelItem); for (int j = 0; j < levelItem->childCount(); ++j) { auto checkItem = levelItem->child(j); auto checkName = checkItem->data(0, CheckRole).toString(); if (insideLevel) { if (checkItem->checkState(0) == Qt::Unchecked) { checksList += QStringLiteral("no-%1").arg(checkName); } } else { if (checkItem->checkState(0) == Qt::Checked) { checksList += checkName; } } } } return checksList; } void ChecksWidget::updateChecks() { QStringList checksList; QList levelItems; // Here we try to find "best" (shortest) checks representation. To do this we build checks list // for every level and test it's size. for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { auto levelItem = m_ui->checksTree->topLevelItem(i); auto levelName = levelItem->data(0, CheckRole).toString(); if (levelName == QStringLiteral("manual")) { // Manual level is "fake level" so we clear the name and will store only // selected checks. levelItems.clear(); levelName.clear(); } else { levelItems += levelItem; } auto levelList = levelChecks(m_ui->checksTree, levelName, levelItems); if (checksList.isEmpty() || checksList.size() > levelList.size()) { checksList = levelList; } } m_ui->messageLabel->setVisible(checksList.isEmpty()); auto checks = checksList.join(QLatin1Char(',')); if (m_checks != checks) { m_checks = checks; emit checksChanged(m_checks); } } void ChecksWidget::setState(QTreeWidgetItem* item, Qt::CheckState state, bool force) { Q_ASSERT(item); QSignalBlocker blocker(m_ui->checksTree); if (item->type() == LevelType) { if (state == Qt::Checked) { // When we enable some non-manual level item, we should also try to enable all // upper level items. We enable upper item only when it's state is Qt::Unchecked. // If the state is Qt::PartiallyChecked we assume that it was configured earlier and // we should skip the item to keep user's checks selection. const int index = m_ui->checksTree->indexOfTopLevelItem(item); if (index > 0 && index < (m_ui->checksTree->topLevelItemCount() - 1)) { setState(m_ui->checksTree->topLevelItem(index - 1), state, false); } if (item->checkState(0) != Qt::Unchecked && !force) { return; } } item->setCheckState(0, state); if (state != Qt::PartiallyChecked) { for (int i = 0; i < item->childCount(); ++i) { item->child(i)->setCheckState(0, state); } } return; } item->setCheckState(0, state); auto levelItem = item->parent(); Q_ASSERT(levelItem); const int childCount = levelItem->childCount(); int checkedCount = 0; for (int i = 0; i < childCount; ++i) { if (levelItem->child(i)->checkState(0) == Qt::Checked) { ++checkedCount; } } if (checkedCount == 0) { setState(levelItem, Qt::Unchecked); } else if (checkedCount == childCount) { setState(levelItem, Qt::Checked); } else { setState(levelItem, Qt::PartiallyChecked); } } void ChecksWidget::searchUpdated(const QString& searchString) { if (searchString.isEmpty()) { m_ui->checksTree->collapseAll(); m_ui->checksTree->setCurrentItem(nullptr); return; } m_ui->checksTree->expandAll(); QTreeWidgetItem* firstVisibleLevel = nullptr; for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { auto levelItem = m_ui->checksTree->topLevelItem(i); if (levelItem->isHidden()) { continue; } if (!firstVisibleLevel) { firstVisibleLevel = levelItem; } for (int j = 0; j < levelItem->childCount(); ++j) { auto checkItem = levelItem->child(j); if (!checkItem->isHidden()) { m_ui->checksTree->setCurrentItem(checkItem); return; } } } m_ui->checksTree->setCurrentItem(firstVisibleLevel); } } diff --git a/plugins/clazy/problemmodel.cpp b/plugins/clazy/problemmodel.cpp index c55b847c3c..9259dbda50 100644 --- a/plugins/clazy/problemmodel.cpp +++ b/plugins/clazy/problemmodel.cpp @@ -1,168 +1,169 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemmodel.h" #include "plugin.h" #include "utils.h" #include #include +#include #include #include namespace Clazy { inline KDevelop::ProblemModelSet* problemModelSet() { return KDevelop::ICore::self()->languageController()->problemModelSet(); } inline QString problemModelId() { return QStringLiteral("clazy"); } ProblemModel::ProblemModel(Plugin* plugin) : KDevelop::ProblemModel(plugin) , m_plugin(plugin) , m_project(nullptr) , m_pathLocation(KDevelop::DocumentRange::invalid()) { setFeatures(CanDoFullUpdate | ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter| ShowSource); reset(); problemModelSet()->addModel(problemModelId(), i18n("Clazy"), this); } ProblemModel::~ProblemModel() { problemModelSet()->removeModel(problemModelId()); } KDevelop::IProject* ProblemModel::project() const { return m_project; } void ProblemModel::setMessage(const QString& message) { setPlaceholderText(message, m_pathLocation, i18n("Clazy")); } // The code is adapted version of cppcheck::ProblemModel::problemExists() // TODO Add into KDevelop::ProblemModel class ? bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) { for (const auto& problem : qAsConst(m_problems)) { if (newProblem->source() == problem->source() && newProblem->sourceString() == problem->sourceString() && newProblem->severity() == problem->severity() && newProblem->finalLocation() == problem->finalLocation() && newProblem->description() == problem->description() && newProblem->explanation() == problem->explanation()) return true; } return false; } // The code is adapted version of cppcheck::ProblemModel::addProblems() // TODO Add into KDevelop::ProblemModel class ? void ProblemModel::addProblems(const QVector& problems) { static int maxLength = 0; if (m_problems.isEmpty()) { maxLength = 0; } for (const auto& problem : problems) { if (problemExists(problem)) { continue; } m_problems.append(problem); addProblem(problem); // This performs adjusting of columns width in the ProblemsView if (maxLength < problem->description().length()) { maxLength = problem->description().length(); setProblems(m_problems); } } } void ProblemModel::setProblems() { if (m_problems.isEmpty()) { setMessage(i18n("Analysis completed, no problems detected.")); } else { setProblems(m_problems); } } void ProblemModel::reset() { reset(nullptr, QString()); } void ProblemModel::reset(KDevelop::IProject* project, const QString& path) { m_project = project; m_path = path; m_pathLocation.document = KDevelop::IndexedString(path); clearProblems(); m_problems.clear(); QString tooltip; if (m_project) { setMessage(i18n("Analysis started...")); tooltip = i18nc("@info:tooltip %1 is the path of the file", "Re-run last Clazy analysis (%1)", prettyPathName(m_path)); } else { tooltip = i18nc("@info:tooltip", "Re-run last Clazy analysis"); } setFullUpdateTooltip(tooltip); } void ProblemModel::show() { problemModelSet()->showModel(problemModelId()); } void ProblemModel::forceFullUpdate() { if (m_project && !m_plugin->isRunning()) { m_plugin->runClazy(m_project, m_path); } } }