Files
qt-creator/src/plugins/clangtools/clangselectablefilesdialog.cpp

396 lines
14 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "clangselectablefilesdialog.h"
#include "ui_clangselectablefilesdialog.h"
#include "ui_clangtoolsbasicsettings.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolssettings.h"
#include "clangtoolsutils.h"
#include <cpptools/compileroptionsbuilder.h>
#include <cpptools/cppcodemodelsettings.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cpptoolsreuse.h>
#include <cpptools/projectinfo.h>
#include <cpptools/projectpart.h>
#include <projectexplorer/selectablefilesmodel.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QPushButton>
using namespace CppTools;
using namespace Utils;
using namespace ProjectExplorer;
namespace ClangTools {
namespace Internal {
class TreeWithFileInfo : public Tree
{
public:
FileInfo info;
};
static void linkDirNode(Tree *parentNode, Tree *childNode)
{
parentNode->childDirectories.append(childNode);
childNode->parent = parentNode;
}
static void linkFileNode(Tree *parentNode, Tree *childNode)
{
childNode->parent = parentNode;
parentNode->files.append(childNode);
parentNode->visibleFiles.append(childNode);
}
static Tree *createDirNode(const QString &name, const FileName &filePath = FileName())
{
auto node = new Tree;
node->name = name;
node->fullPath = filePath;
node->isDir = true;
return node;
}
static Tree *createFileNode(const FileInfo &fileInfo, bool displayFullPath = false)
{
auto node = new TreeWithFileInfo;
node->name = displayFullPath ? fileInfo.file.toString() : fileInfo.file.fileName();
node->fullPath = fileInfo.file;
node->info = fileInfo;
return node;
}
class SelectableFilesModel : public ProjectExplorer::SelectableFilesModel
{
Q_OBJECT
public:
SelectableFilesModel(const CppTools::ProjectInfo &projectInfo, const FileInfos &allFileInfos)
: ProjectExplorer::SelectableFilesModel(nullptr)
{
buildTree(projectInfo.project(), allFileInfos);
}
// Returns the minimal selection that can restore all selected files.
//
// For example, if a directory node if fully checked, there is no need to
// save all the children of that node.
void minimalSelection(QSet<FileName> &checkedDirs, QSet<FileName> &checkedFiles) const
{
traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
auto node = static_cast<Tree *>(index.internalPointer());
if (node->checked == Qt::Checked) {
if (node->isDir) {
checkedDirs += node->fullPath;
return false; // Do not descend further.
}
checkedFiles += node->fullPath;
}
return true;
});
}
void restoreMinimalSelection(const QSet<FileName> &dirs, const QSet<FileName> &files)
{
if (dirs.isEmpty() && files.isEmpty())
return;
traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
auto node = static_cast<Tree *>(index.internalPointer());
if (node->isDir && dirs.contains(node->fullPath)) {
setData(index, Qt::Checked, Qt::CheckStateRole);
return false; // Do not descend further.
}
if (!node->isDir && files.contains(node->fullPath))
setData(index, Qt::Checked, Qt::CheckStateRole);
return true;
});
}
FileInfos selectedFileInfos() const
{
FileInfos result;
traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
auto node = static_cast<Tree *>(index.internalPointer());
if (node->checked == Qt::Unchecked)
return false;
if (!node->isDir)
result.push_back(static_cast<TreeWithFileInfo *>(node)->info);
return true;
});
return result;
}
private:
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);
}
}
void buildTree(ProjectExplorer::Project *project, const FileInfos &fileInfos)
{
m_root->fullPath = project->projectFilePath();
m_root->name = project->projectFilePath().fileName();
m_root->isDir = true;
FileInfos outOfBaseDirFiles;
Tree *projectDirTree = buildProjectDirTree(project->projectDirectory(),
fileInfos,
outOfBaseDirFiles);
if (outOfBaseDirFiles.empty()) {
// Showing the project file and beneath the project dir is pointless in this case,
// so get rid of the root node and modify the project dir node as the new root node.
projectDirTree->name = m_root->name;
projectDirTree->fullPath = m_root->fullPath;
projectDirTree->parent = m_root->parent;
delete m_root; // OK, it has no files / child dirs.
m_root = projectDirTree;
} else {
// Set up project dir node as sub node of the project file node
linkDirNode(m_root, projectDirTree);
// Add files outside of the base directory to a separate node
Tree *externalFilesNode = createDirNode(SelectableFilesDialog::tr(
"Files outside of the base directory"),
FileName::fromString("/"));
linkDirNode(m_root, externalFilesNode);
for (const FileInfo &fileInfo : outOfBaseDirFiles)
linkFileNode(externalFilesNode, createFileNode(fileInfo, true));
}
}
Tree *buildProjectDirTree(const FileName &projectDir,
const FileInfos &fileInfos,
FileInfos &outOfBaseDirFiles) const
{
Tree *projectDirNode = createDirNode(projectDir.fileName(), projectDir);
QHash<FileName, Tree *> dirsToNode;
dirsToNode.insert(projectDirNode->fullPath, projectDirNode);
for (const FileInfo &fileInfo : fileInfos) {
if (!fileInfo.file.isChildOf(projectDirNode->fullPath)) {
outOfBaseDirFiles.push_back(fileInfo);
continue; // Handle these separately.
}
// Find or create parent nodes
FileName parentDir = fileInfo.file.parentDir();
Tree *parentNode = dirsToNode[parentDir];
if (!parentNode) {
// Find nearest existing node
QStringList dirsToCreate;
while (!parentNode) {
dirsToCreate.prepend(parentDir.fileName());
parentDir = parentDir.parentDir();
parentNode = dirsToNode[parentDir];
}
// Create needed extra dir nodes
FileName currentDirPath = parentDir;
for (const QString &dirName : dirsToCreate) {
currentDirPath = currentDirPath.pathAppended(dirName);
Tree *newDirNode = createDirNode(dirName, currentDirPath);
linkDirNode(parentNode, newDirNode);
dirsToNode.insert(currentDirPath, newDirNode);
parentNode = newDirNode;
}
}
// Create and link file node to dir node
linkFileNode(parentNode, createFileNode(fileInfo));
}
return projectDirNode;
}
};
enum { GlobalSettings , CustomSettings };
static Core::Id diagnosticConfiguration(ClangToolsProjectSettings *settings)
{
Core::Id id = settings->diagnosticConfig();
if (id.isValid())
return id;
return ClangToolsSettings::instance()->savedDiagnosticConfigId();
}
SelectableFilesDialog::SelectableFilesDialog(const ProjectInfo &projectInfo,
const FileInfos &allFileInfos)
: QDialog(nullptr)
, m_ui(new Ui::SelectableFilesDialog)
, m_filesModel(new SelectableFilesModel(projectInfo, allFileInfos))
, m_project(projectInfo.project())
, m_analyzeButton(new QPushButton(tr("Analyze"), this))
{
m_ui->setupUi(this);
m_ui->filesView->setModel(m_filesModel.get());
m_ui->filesView->expandToDepth(2);
m_ui->buttons->setStandardButtons(QDialogButtonBox::Cancel);
m_ui->buttons->addButton(m_analyzeButton, QDialogButtonBox::AcceptRole);
CppTools::ClangDiagnosticConfigsSelectionWidget *diagnosticConfigsSelectionWidget
= m_ui->clangToolsBasicSettings->ui()->clangDiagnosticConfigsSelectionWidget;
QCheckBox *buildBeforeAnalysis = m_ui->clangToolsBasicSettings->ui()->buildBeforeAnalysis;
ClangToolsProjectSettings *settings = ClangToolsProjectSettingsManager::getSettings(m_project);
m_customDiagnosticConfig = diagnosticConfiguration(settings);
m_buildBeforeAnalysis = settings->buildBeforeAnalysis();
if (settings->useGlobalSettings()) {
m_ui->globalOrCustom->setCurrentIndex(GlobalSettings);
m_ui->clangToolsBasicSettings->setEnabled(false);
diagnosticConfigsSelectionWidget->refresh(
ClangToolsSettings::instance()->savedDiagnosticConfigId());
buildBeforeAnalysis->setCheckState(
ClangToolsSettings::instance()->savedBuildBeforeAnalysis()
? Qt::Checked : Qt::Unchecked);
} else {
m_ui->globalOrCustom->setCurrentIndex(CustomSettings);
m_ui->clangToolsBasicSettings->setEnabled(true);
diagnosticConfigsSelectionWidget->refresh(m_customDiagnosticConfig);
buildBeforeAnalysis->setCheckState(m_buildBeforeAnalysis ? Qt::Checked : Qt::Unchecked);
}
connect(m_ui->globalOrCustom,
QOverload<int>::of(&QComboBox::currentIndexChanged),
[=](int index){
m_ui->clangToolsBasicSettings->setEnabled(index == CustomSettings);
if (index == CustomSettings) {
diagnosticConfigsSelectionWidget->refresh(m_customDiagnosticConfig);
buildBeforeAnalysis->setCheckState(m_buildBeforeAnalysis ? Qt::Checked : Qt::Unchecked);
} else {
diagnosticConfigsSelectionWidget->refresh(
ClangToolsSettings::instance()->savedDiagnosticConfigId());
buildBeforeAnalysis->setCheckState(
ClangToolsSettings::instance()->savedBuildBeforeAnalysis()
? Qt::Checked : Qt::Unchecked);
}
});
connect(diagnosticConfigsSelectionWidget,
&ClangDiagnosticConfigsSelectionWidget::currentConfigChanged,
[this](const Core::Id &currentConfigId) {
if (m_ui->globalOrCustom->currentIndex() == CustomSettings)
m_customDiagnosticConfig = currentConfigId;
});
connect(buildBeforeAnalysis, &QCheckBox::toggled, [this](bool checked) {
if (m_ui->globalOrCustom->currentIndex() == CustomSettings)
m_buildBeforeAnalysis = checked;
});
// Restore selection
if (settings->selectedDirs().isEmpty() && settings->selectedFiles().isEmpty())
m_filesModel->selectAllFiles(); // Initially, all files are selected
else // Restore selection
m_filesModel->restoreMinimalSelection(settings->selectedDirs(), settings->selectedFiles());
m_analyzeButton->setEnabled(m_filesModel->hasCheckedFiles());
connect(m_filesModel.get(), &QAbstractItemModel::dataChanged, [this]() {
m_analyzeButton->setEnabled(m_filesModel->hasCheckedFiles());
});
connect(CppTools::codeModelSettings().data(), &CppTools::CppCodeModelSettings::changed,
this, [=]() {
if (m_ui->globalOrCustom->currentIndex() == CustomSettings) {
diagnosticConfigsSelectionWidget->refresh(m_customDiagnosticConfig);
} else {
diagnosticConfigsSelectionWidget->refresh(
ClangToolsSettings::instance()->savedDiagnosticConfigId());
}
});
}
SelectableFilesDialog::~SelectableFilesDialog() = default;
FileInfos SelectableFilesDialog::filteredFileInfos() const
{
return m_filesModel->selectedFileInfos();
}
void SelectableFilesDialog::accept()
{
ClangToolsProjectSettings *settings = ClangToolsProjectSettingsManager::getSettings(m_project);
// Save diagnostic configuration and flag to build before analysis
settings->setUseGlobalSettings(m_ui->globalOrCustom->currentIndex() == GlobalSettings);
settings->setDiagnosticConfig(m_customDiagnosticConfig);
settings->setBuildBeforeAnalysis(m_buildBeforeAnalysis);
// Save selection
QSet<FileName> checkedDirs;
QSet<FileName> checkedFiles;
m_filesModel->minimalSelection(checkedDirs, checkedFiles);
settings->setSelectedDirs(checkedDirs);
settings->setSelectedFiles(checkedFiles);
QDialog::accept();
}
} // namespace Internal
} // namespace ClangTools
#include "clangselectablefilesdialog.moc"