diff options
author | Ivan Donchevskii <ivan.donchevskii@qt.io> | 2018-05-16 07:58:53 +0200 |
---|---|---|
committer | Ivan Donchevskii <ivan.donchevskii@qt.io> | 2018-05-25 09:59:20 +0000 |
commit | bc458c7b5f36802ac35d4e4022c966929b84da61 (patch) | |
tree | 6d85e1f27a39dba01b4a9fad562b8ac2774bb5d6 /src/plugins/cpptools/clangdiagnosticconfigswidget.cpp | |
parent | b1fcbfecceafdf107bfebd95f773013b551088a1 (diff) | |
download | qt-creator-bc458c7b5f36802ac35d4e4022c966929b84da61.tar.gz |
Clang: Use the tree instead of the list for Clang-Tidy settings
List of checks does not give enough flexibility to
select/unselect specific checks. The tree fixes that.
Also remove Clang-Tidy checks line edit because it is
now integrated into the tree mode as an alternative way
of providing checks by pressing "Plain text edit" button.
'cpptools_clangtidychecks.h' is generated using python
script 'generateClangTidyChecks.py' and clang-tidy
from our LLVM/Clang 6.0 build.
Change-Id: I2ed1738cb2cbbf8dac6aba563469f06f69b11593
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Diffstat (limited to 'src/plugins/cpptools/clangdiagnosticconfigswidget.cpp')
-rw-r--r-- | src/plugins/cpptools/clangdiagnosticconfigswidget.cpp | 233 |
1 files changed, 185 insertions, 48 deletions
diff --git a/src/plugins/cpptools/clangdiagnosticconfigswidget.cpp b/src/plugins/cpptools/clangdiagnosticconfigswidget.cpp index 169362d397..22b2950e5a 100644 --- a/src/plugins/cpptools/clangdiagnosticconfigswidget.cpp +++ b/src/plugins/cpptools/clangdiagnosticconfigswidget.cpp @@ -26,28 +26,169 @@ #include "clangdiagnosticconfigswidget.h" #include "cppcodemodelsettings.h" +#include "cpptools_clangtidychecks.h" #include "cpptoolsreuse.h" #include "ui_clangdiagnosticconfigswidget.h" #include "ui_clangbasechecks.h" #include "ui_clazychecks.h" #include "ui_tidychecks.h" +#include <projectexplorer/selectablefilesmodel.h> + #include <utils/algorithm.h> #include <utils/qtcassert.h> #include <utils/utilsicons.h> #include <QDebug> +#include <QDialogButtonBox> #include <QInputDialog> #include <QPushButton> #include <QUuid> namespace CppTools { +static void buildTree(ProjectExplorer::Tree *parent, + ProjectExplorer::Tree *current, + const Constants::TidyNode &node) +{ + current->name = QString::fromUtf8(node.name); + current->isDir = node.children.size(); + if (parent) { + current->fullPath = parent->fullPath + current->name; + parent->childDirectories.push_back(current); + } else { + current->fullPath = Utils::FileName::fromString(current->name); + } + current->parent = parent; + for (const Constants::TidyNode &nodeChild : node.children) + buildTree(current, new ProjectExplorer::Tree, nodeChild); +} + +class TidyChecksTreeModel final : public ProjectExplorer::SelectableFilesModel +{ + Q_OBJECT + +public: + TidyChecksTreeModel() + : ProjectExplorer::SelectableFilesModel(nullptr) + { + buildTree(nullptr, m_root, Constants::CLANG_TIDY_CHECKS_ROOT); + } + + QString selectedChecks() const + { + QString checks; + collectChecks(m_root, checks); + return "-*" + checks; + } + + void selectChecks(const QString &checks) + { + m_root->checked = Qt::Unchecked; + propagateDown(index(0, 0, QModelIndex())); + + QStringList checksList = checks.simplified().remove(" ") + .split(",", QString::SkipEmptyParts); + + for (QString &check : checksList) { + Qt::CheckState state; + if (check.startsWith("-")) { + check = check.right(check.length() - 1); + state = Qt::Unchecked; + } else { + state = Qt::Checked; + } + const QModelIndex index = indexForCheck(check); + if (!index.isValid()) + continue; + auto node = static_cast<ProjectExplorer::Tree *>(index.internalPointer()); + node->checked = state; + propagateUp(index); + propagateDown(index); + } + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final + { + if (!index.isValid() || role == Qt::DecorationRole) + return QVariant(); + if (role == Qt::DisplayRole) { + auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer()); + return node->isDir ? (node->name + "*") : node->name; + } + return ProjectExplorer::SelectableFilesModel::data(index, role); + } + +private: + + // TODO: Remove/replace this method after base class refactoring is done. + void traverse(const QModelIndex &index, + const std::function<bool(const QModelIndex &)> &visit) const + { + if (!index.isValid()) + return; + + if (!visit(index)) + return; + + if (!hasChildren(index)) + return; + + const int rows = rowCount(index); + const int cols = columnCount(index); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) + traverse(this->index(i, j, index), visit); + } + } + + QModelIndex indexForCheck(const QString &check) const { + if (check == "*") + return index(0, 0, QModelIndex()); + + QModelIndex result; + traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ + using ProjectExplorer::Tree; + if (result.isValid()) + return false; + + auto *node = static_cast<Tree *>(index.internalPointer()); + const QString nodeName = node->fullPath.toString(); + if ((check.endsWith("*") && nodeName.startsWith(check.left(check.length() - 1))) + || (!node->isDir && nodeName == check)) { + result = index; + return false; + } + + if (!check.startsWith(nodeName)) + return false; + + return true; + }); + return result; + } + + static void collectChecks(const ProjectExplorer::Tree *root, QString &checks) + { + if (root->checked == Qt::Unchecked) + return; + if (root->checked == Qt::Checked) { + checks += "," + root->fullPath.toString(); + if (root->isDir) + checks += "*"; + return; + } + for (const ProjectExplorer::Tree *t : root->childDirectories) + collectChecks(t, checks); + } +}; + ClangDiagnosticConfigsWidget::ClangDiagnosticConfigsWidget(const Core::Id &configToSelect, QWidget *parent) : QWidget(parent) , m_ui(new Ui::ClangDiagnosticConfigsWidget) , m_diagnosticConfigsModel(codeModelSettings()->clangCustomDiagnosticConfigs()) + , m_tidyTreeModel(new TidyChecksTreeModel()) { m_ui->setupUi(this); setupTabs(); @@ -134,22 +275,10 @@ void ClangDiagnosticConfigsWidget::onClangTidyModeChanged(int index) syncClangTidyWidgets(config); } -void ClangDiagnosticConfigsWidget::onClangTidyItemChanged(QListWidgetItem *item) +void ClangDiagnosticConfigsWidget::onClangTidyTreeChanged() { - const QString prefix = item->text(); ClangDiagnosticConfig config = selectedConfig(); - QString checks = config.clangTidyChecksPrefixes(); - item->checkState() == Qt::Checked - ? checks.append(',' + prefix) - : checks.remove(',' + prefix); - config.setClangTidyChecksPrefixes(checks); - updateConfig(config); -} - -void ClangDiagnosticConfigsWidget::onClangTidyLineEdited(const QString &text) -{ - ClangDiagnosticConfig config = selectedConfig(); - config.setClangTidyChecksString(text); + config.setClangTidyChecks(m_tidyTreeModel->selectedChecks()); updateConfig(config); } @@ -302,18 +431,13 @@ void ClangDiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticCon switch (tidyMode) { case ClangDiagnosticConfig::TidyMode::Disabled: case ClangDiagnosticConfig::TidyMode::File: - m_tidyChecks->checksString->setVisible(false); - m_tidyChecks->checksListWrapper->setCurrentIndex(1); - break; - case ClangDiagnosticConfig::TidyMode::ChecksString: - m_tidyChecks->checksString->setVisible(true); + m_tidyChecks->plainTextEditButton->setVisible(false); m_tidyChecks->checksListWrapper->setCurrentIndex(1); - m_tidyChecks->checksString->setText(config.clangTidyChecksString()); break; case ClangDiagnosticConfig::TidyMode::ChecksPrefixList: - m_tidyChecks->checksString->setVisible(false); + m_tidyChecks->plainTextEditButton->setVisible(true); m_tidyChecks->checksListWrapper->setCurrentIndex(0); - syncTidyChecksList(config); + syncTidyChecksToTree(config); break; } @@ -321,25 +445,9 @@ void ClangDiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticCon connectClangTidyItemChanged(); } -void ClangDiagnosticConfigsWidget::syncTidyChecksList(const ClangDiagnosticConfig &config) +void ClangDiagnosticConfigsWidget::syncTidyChecksToTree(const ClangDiagnosticConfig &config) { - const QString tidyChecks = config.clangTidyChecksPrefixes(); - for (int row = 0; row < m_tidyChecks->checksPrefixesList->count(); ++row) { - QListWidgetItem *item = m_tidyChecks->checksPrefixesList->item(row); - - Qt::ItemFlags flags = item->flags(); - flags |= Qt::ItemIsUserCheckable; - if (config.isReadOnly()) - flags &= ~Qt::ItemIsEnabled; - else - flags |= Qt::ItemIsEnabled; - item->setFlags(flags); - - if (tidyChecks.indexOf(item->text()) != -1) - item->setCheckState(Qt::Checked); - else - item->setCheckState(Qt::Unchecked); - } + m_tidyTreeModel->selectChecks(config.clangTidyChecks()); } void ClangDiagnosticConfigsWidget::syncClazyWidgets(const ClangDiagnosticConfig &config) @@ -411,10 +519,8 @@ void ClangDiagnosticConfigsWidget::connectClangTidyItemChanged() static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClangDiagnosticConfigsWidget::onClangTidyModeChanged); - connect(m_tidyChecks->checksPrefixesList, &QListWidget::itemChanged, - this, &ClangDiagnosticConfigsWidget::onClangTidyItemChanged); - connect(m_tidyChecks->checksString, &QLineEdit::textEdited, - this, &ClangDiagnosticConfigsWidget::onClangTidyLineEdited); + connect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, + this, &ClangDiagnosticConfigsWidget::onClangTidyTreeChanged); } void ClangDiagnosticConfigsWidget::disconnectClangTidyItemChanged() @@ -423,10 +529,8 @@ void ClangDiagnosticConfigsWidget::disconnectClangTidyItemChanged() static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClangDiagnosticConfigsWidget::onClangTidyModeChanged); - disconnect(m_tidyChecks->checksPrefixesList, &QListWidget::itemChanged, - this, &ClangDiagnosticConfigsWidget::onClangTidyItemChanged); - disconnect(m_tidyChecks->checksString, &QLineEdit::textEdited, - this, &ClangDiagnosticConfigsWidget::onClangTidyLineEdited); + disconnect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, + this, &ClangDiagnosticConfigsWidget::onClangTidyTreeChanged); } void ClangDiagnosticConfigsWidget::connectClazyRadioButtonClicked(QRadioButton *button) @@ -493,6 +597,37 @@ void ClangDiagnosticConfigsWidget::setupTabs() m_tidyChecks.reset(new CppTools::Ui::TidyChecks); m_tidyChecksWidget = new QWidget(); m_tidyChecks->setupUi(m_tidyChecksWidget); + m_tidyChecks->checksPrefixesTree->setModel(m_tidyTreeModel.get()); + m_tidyChecks->checksPrefixesTree->expandToDepth(0); + connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() { + QDialog dialog; + dialog.setWindowTitle(tr("Checks")); + dialog.setLayout(new QVBoxLayout); + auto *textEdit = new QTextEdit(&dialog); + dialog.layout()->addWidget(textEdit); + auto *buttonsBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialog.layout()->addWidget(buttonsBox); + QObject::connect(buttonsBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + QObject::connect(buttonsBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + const QString initialChecks = m_tidyTreeModel->selectedChecks(); + textEdit->setPlainText(initialChecks); + + QObject::connect(&dialog, &QDialog::accepted, [=, &initialChecks]() { + const QString updatedChecks = textEdit->toPlainText(); + if (updatedChecks == initialChecks) + return; + + disconnectClangTidyItemChanged(); + + // Also throws away invalid options. + m_tidyTreeModel->selectChecks(updatedChecks); + onClangTidyTreeChanged(); + + connectClangTidyItemChanged(); + }); + dialog.exec(); + }); + connectClangTidyItemChanged(); m_ui->tabWidget->addTab(m_clangBaseChecksWidget, tr("Clang")); @@ -502,3 +637,5 @@ void ClangDiagnosticConfigsWidget::setupTabs() } } // CppTools namespace + +#include "clangdiagnosticconfigswidget.moc" |