/**************************************************************************** ** ** 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 "clangtoolsprojectsettings.h" #include "clangtoolssettings.h" #include "clangtoolsutils.h" #include #include #include #include #include #include #include #include #include #include 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 &checkedDirs, QSet &checkedFiles) const { traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ auto node = static_cast(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 &dirs, const QSet &files) { if (dirs.isEmpty() && files.isEmpty()) return; traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ auto node = static_cast(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(index.internalPointer()); if (node->checked == Qt::Unchecked) return false; if (!node->isDir) result += static_cast(node)->info; return true; }); return result; } private: void traverse(const QModelIndex &index, const std::function &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.isEmpty()) { // 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 dirsToNode; dirsToNode.insert(projectDirNode->fullPath, projectDirNode); for (const FileInfo &fileInfo : fileInfos) { if (!fileInfo.file.isChildOf(projectDirNode->fullPath)) { outOfBaseDirFiles += 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.appendPath(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) { if (settings->useGlobalSettings()) return ClangToolsSettings::instance()->savedDiagnosticConfigId(); return settings->diagnosticConfig(); } 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); m_ui->diagnosticConfigsSelectionWidget->showLabel(false); ClangToolsProjectSettings *settings = ClangToolsProjectSettingsManager::getSettings(m_project); if (settings->useGlobalSettings()) { m_ui->globalOrCustom->setCurrentIndex(GlobalSettings); m_ui->diagnosticConfigsSelectionWidget->setEnabled(false); } else { m_ui->globalOrCustom->setCurrentIndex(CustomSettings); m_ui->diagnosticConfigsSelectionWidget->setEnabled(true); } m_customDiagnosticConfig = diagnosticConfiguration(settings); m_ui->diagnosticConfigsSelectionWidget->refresh(m_customDiagnosticConfig); connect(m_ui->globalOrCustom, static_cast(&QComboBox::currentIndexChanged), [this](int index){ m_ui->diagnosticConfigsSelectionWidget->setEnabled(index == CustomSettings); if (index == CustomSettings) { m_ui->diagnosticConfigsSelectionWidget->refresh(m_customDiagnosticConfig); } else { m_ui->diagnosticConfigsSelectionWidget->refresh( ClangToolsSettings::instance()->savedDiagnosticConfigId()); } }); connect(m_ui->diagnosticConfigsSelectionWidget, &ClangDiagnosticConfigsSelectionWidget::currentConfigChanged, [this](const Core::Id ¤tConfigId) { if (m_ui->globalOrCustom->currentIndex() == CustomSettings) m_customDiagnosticConfig = currentConfigId; }); // 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, [=]() { m_ui->diagnosticConfigsSelectionWidget->refresh(diagnosticConfiguration(settings)); }); } SelectableFilesDialog::~SelectableFilesDialog() {} FileInfos SelectableFilesDialog::filteredFileInfos() const { return m_filesModel->selectedFileInfos(); } void SelectableFilesDialog::accept() { ClangToolsProjectSettings *settings = ClangToolsProjectSettingsManager::getSettings(m_project); // Save diagnostic configuration settings->setUseGlobalSettings(m_ui->globalOrCustom->currentIndex() == GlobalSettings); settings->setDiagnosticConfig(m_customDiagnosticConfig); // Save selection QSet checkedDirs; QSet checkedFiles; m_filesModel->minimalSelection(checkedDirs, checkedFiles); settings->setSelectedDirs(checkedDirs); settings->setSelectedFiles(checkedFiles); QDialog::accept(); } } // namespace Internal } // namespace ClangTools #include "clangselectablefilesdialog.moc"