File indexing completed on 2024-05-12 04:39:40

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later
0006 */
0007 
0008 #include "projectpathswidget.h"
0009 
0010 #include <QFileDialog>
0011 #include <QPointer>
0012 #include <QRegExp>
0013 
0014 #include <interfaces/iproject.h>
0015 
0016 #include "../compilerprovider/compilerprovider.h"
0017 #include "../compilerprovider/settingsmanager.h"
0018 
0019 #include "ui_projectpathswidget.h"
0020 #include "ui_batchedit.h"
0021 #include "projectpathsmodel.h"
0022 #include <debug.h>
0023 
0024 #include <KMessageBox>
0025 #include <KMessageBox_KDevCompat>
0026 #include <KLocalizedString>
0027 
0028 
0029 using namespace KDevelop;
0030 
0031 namespace
0032 {
0033 enum PageType {
0034     IncludesPage,
0035     DefinesPage,
0036     ParserArgumentsPage
0037 };
0038 }
0039 
0040 ProjectPathsWidget::ProjectPathsWidget( QWidget* parent )
0041     : QWidget(parent),
0042       ui(new Ui::ProjectPathsWidget),
0043       pathsModel(new ProjectPathsModel(this))
0044 {
0045     ui->setupUi( this );
0046 
0047     // hack taken from kurlrequester, make the buttons a bit less in height so they better match the url-requester
0048     ui->addPath->setFixedHeight( ui->projectPaths->sizeHint().height() );
0049     ui->removePath->setFixedHeight( ui->projectPaths->sizeHint().height() );
0050 
0051     connect( ui->addPath, &QPushButton::clicked, this, &ProjectPathsWidget::addProjectPath );
0052     connect( ui->removePath, &QPushButton::clicked, this, &ProjectPathsWidget::deleteProjectPath );
0053     connect( ui->batchEdit, &QPushButton::clicked, this, &ProjectPathsWidget::batchEdit );
0054 
0055     ui->projectPaths->setModel( pathsModel );
0056     connect( ui->projectPaths, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &ProjectPathsWidget::projectPathSelected );
0057     connect( pathsModel, &ProjectPathsModel::dataChanged, this, &ProjectPathsWidget::changed );
0058     connect( pathsModel, &ProjectPathsModel::rowsInserted, this, &ProjectPathsWidget::changed );
0059     connect(pathsModel, &ProjectPathsModel::rowsRemoved, this, &ProjectPathsWidget::changed);
0060     connect(ui->compiler, &QComboBox::textActivated, this, &ProjectPathsWidget::changed);
0061     connect(ui->compiler, &QComboBox::textActivated, this, &ProjectPathsWidget::changeCompilerForPath);
0062 
0063     connect( ui->includesWidget, QOverload<const QStringList&>::of(&IncludesWidget::includesChanged), this, &ProjectPathsWidget::includesChanged );
0064     connect( ui->definesWidget, QOverload<const KDevelop::Defines&>::of(&DefinesWidget::definesChanged), this, &ProjectPathsWidget::definesChanged );
0065 
0066     connect(ui->languageParameters, &QTabWidget::currentChanged, this, &ProjectPathsWidget::tabChanged);
0067 
0068     connect(ui->parserWidget, &ParserWidget::changed, this, &ProjectPathsWidget::parserArgumentsChanged);
0069 
0070     tabChanged(IncludesPage);
0071 }
0072 
0073 ProjectPathsWidget::~ProjectPathsWidget()
0074 {
0075 }
0076 
0077 QVector<ConfigEntry> ProjectPathsWidget::paths() const
0078 {
0079     return pathsModel->paths();
0080 }
0081 
0082 void ProjectPathsWidget::setPaths( const QVector<ConfigEntry>& paths )
0083 {
0084     bool b = blockSignals( true );
0085     clear();
0086     pathsModel->setPaths( paths );
0087     blockSignals( b );
0088     ui->projectPaths->setCurrentIndex(0); // at least a project root item is present
0089     ui->languageParameters->setCurrentIndex(0);
0090 
0091     // Set compilers
0092     ui->compiler->clear();
0093     auto settings = SettingsManager::globalInstance();
0094     auto compilers = settings->provider()->compilers();
0095     for (int i = 0 ; i < compilers.count(); ++i) {
0096         Q_ASSERT(compilers[i]);
0097         if (!compilers[i]) {
0098             continue;
0099         }
0100         ui->compiler->addItem(compilers[i]->name());
0101         QVariant val; val.setValue(compilers[i]);
0102         ui->compiler->setItemData(i, val);
0103     }
0104 
0105     projectPathSelected(0);
0106     updateEnablements();
0107 }
0108 
0109 void ProjectPathsWidget::definesChanged( const Defines& defines )
0110 {
0111     qCDebug(DEFINESANDINCLUDES) << "defines changed";
0112     updatePathsModel( QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole );
0113 }
0114 
0115 void ProjectPathsWidget::includesChanged( const QStringList& includes )
0116 {
0117     qCDebug(DEFINESANDINCLUDES) << "includes changed";
0118     updatePathsModel( includes, ProjectPathsModel::IncludesDataRole );
0119 }
0120 
0121 void ProjectPathsWidget::parserArgumentsChanged()
0122 {
0123     updatePathsModel(QVariant::fromValue(ui->parserWidget->parserArguments()), ProjectPathsModel::ParserArgumentsRole);
0124 }
0125 
0126 void ProjectPathsWidget::updatePathsModel(const QVariant& newData, int role)
0127 {
0128     QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0, QModelIndex() );
0129     if( idx.isValid() ) {
0130         bool b = pathsModel->setData( idx, newData, role );
0131         if( b ) {
0132             emit changed();
0133         }
0134     }
0135 }
0136 
0137 void ProjectPathsWidget::projectPathSelected( int index )
0138 {
0139     if( index < 0 && pathsModel->rowCount() > 0 ) {
0140         index = 0;
0141     }
0142     Q_ASSERT(index >= 0);
0143     const QModelIndex midx = pathsModel->index( index, 0 );
0144     ui->includesWidget->setIncludes( pathsModel->data( midx, ProjectPathsModel::IncludesDataRole ).toStringList() );
0145     ui->definesWidget->setDefines( pathsModel->data( midx, ProjectPathsModel::DefinesDataRole ).value<Defines>() );
0146 
0147     Q_ASSERT(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value<CompilerPointer>());
0148 
0149     ui->compiler->setCurrentText(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value<CompilerPointer>()->name());
0150 
0151     ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole).value<ParserArguments>());
0152 
0153     updateEnablements();
0154 }
0155 
0156 void ProjectPathsWidget::clear()
0157 {
0158     bool sigDisabled = ui->projectPaths->blockSignals( true );
0159     pathsModel->setPaths({});
0160     ui->includesWidget->clear();
0161     ui->definesWidget->clear();
0162     updateEnablements();
0163     ui->projectPaths->blockSignals( sigDisabled );
0164 }
0165 
0166 void ProjectPathsWidget::addProjectPath()
0167 {
0168     const QUrl directory = pathsModel->data(pathsModel->index(0, 0), ProjectPathsModel::FullUrlDataRole).toUrl();
0169     QPointer<QFileDialog> dlg = new QFileDialog(this, i18nc("@title:window", "Select Project Path"), directory.toLocalFile());
0170     dlg->setFileMode(QFileDialog::Directory);
0171     dlg->setOption(QFileDialog::ShowDirsOnly);
0172     if (dlg->exec()) {
0173         pathsModel->addPath(dlg->selectedUrls().value(0));
0174         ui->projectPaths->setCurrentIndex(pathsModel->rowCount() - 1);
0175         updateEnablements();
0176     }
0177     delete dlg;
0178 }
0179 
0180 void ProjectPathsWidget::deleteProjectPath()
0181 {
0182     const QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0 );
0183     if (KMessageBox::questionTwoActions(this,
0184                                         i18n("Are you sure you want to delete the configuration for the path '%1'?",
0185                                              pathsModel->data(idx, Qt::DisplayRole).toString()),
0186                                         i18nc("@title:window", "Delete Path Configuration"), KStandardGuiItem::del(),
0187                                         KStandardGuiItem::cancel())
0188         == KMessageBox::PrimaryAction) {
0189         pathsModel->removeRows( ui->projectPaths->currentIndex(), 1 );
0190     }
0191     updateEnablements();
0192 }
0193 
0194 void ProjectPathsWidget::setProject(KDevelop::IProject* w_project)
0195 {
0196     pathsModel->setProject( w_project );
0197     ui->includesWidget->setProject( w_project );
0198 }
0199 
0200 void ProjectPathsWidget::updateEnablements() {
0201     // Disable removal of the project root entry which is always first in the list
0202     ui->removePath->setEnabled( ui->projectPaths->currentIndex() > 0 );
0203 }
0204 
0205 void ProjectPathsWidget::batchEdit()
0206 {
0207     Ui::BatchEdit be;
0208     QPointer<QDialog> dialog = new QDialog(this);
0209     be.setupUi(dialog);
0210 
0211     const int index = qMax(ui->projectPaths->currentIndex(), 0);
0212 
0213     const QModelIndex midx = pathsModel->index(index, 0);
0214 
0215     if (!midx.isValid()) {
0216         return;
0217     }
0218 
0219     bool includesTab = ui->languageParameters->currentIndex() == 0;
0220     if (includesTab) {
0221         auto includes = pathsModel->data(midx, ProjectPathsModel::IncludesDataRole).toStringList();
0222         be.textEdit->setPlainText(includes.join(QLatin1Char('\n')));
0223 
0224         dialog->setWindowTitle(i18nc("@title:window", "Edit Include Directories/Files"));
0225     } else {
0226         auto defines = pathsModel->data(midx, ProjectPathsModel::DefinesDataRole).value<Defines>();
0227 
0228         for (auto it = defines.constBegin(); it != defines.constEnd(); it++) {
0229             be.textEdit->appendPlainText(it.key() + QLatin1Char('=') + it.value());
0230         }
0231 
0232         dialog->setWindowTitle(i18nc("@title:window", "Edit Defined Macros"));
0233     }
0234 
0235     if (dialog->exec() != QDialog::Accepted) {
0236         delete dialog;
0237         return;
0238     }
0239 
0240     if (includesTab) {
0241         auto includes = be.textEdit->toPlainText().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0242         for (auto& s : includes) {
0243             s = s.trimmed();
0244         }
0245 
0246         pathsModel->setData(midx, includes, ProjectPathsModel::IncludesDataRole);
0247     } else {
0248         auto list = be.textEdit->toPlainText().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0249         Defines defines;
0250 
0251         for (auto& d : list) {
0252             //This matches: a=b, a=, a
0253             QRegExp r(QStringLiteral("^([^=]+)(=(.*))?$"));
0254 
0255             if (!r.exactMatch(d)) {
0256                 continue;
0257             }
0258             defines[r.cap(1).trimmed()] = r.cap(3).trimmed();
0259         }
0260 
0261         pathsModel->setData(midx, QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole);
0262     }
0263 
0264     projectPathSelected(index);
0265     delete dialog;
0266 }
0267 
0268 void ProjectPathsWidget::setCurrentCompiler(const QString& name)
0269 {
0270     for (int i = 0 ; i < ui->compiler->count(); ++i) {
0271         if(ui->compiler->itemText(i) == name)
0272         {
0273             ui->compiler->setCurrentIndex(i);
0274         }
0275     }
0276 }
0277 
0278 CompilerPointer ProjectPathsWidget::currentCompiler() const
0279 {
0280     return ui->compiler->itemData(ui->compiler->currentIndex()).value<CompilerPointer>();
0281 }
0282 
0283 void ProjectPathsWidget::tabChanged(int idx)
0284 {
0285     if (idx == ParserArgumentsPage) {
0286         ui->batchEdit->setVisible(false);
0287         ui->compilerBox->setVisible(true);
0288         ui->configureLabel->setText(i18n("Configure C/C++ parser"));
0289     } else {
0290         ui->batchEdit->setVisible(true);
0291         ui->compilerBox->setVisible(false);
0292         ui->configureLabel->setText(i18n("Configure which macros and include directories/files will be added to the parser during project parsing:"));
0293     }
0294 }
0295 
0296 void ProjectPathsWidget::changeCompilerForPath()
0297 {
0298     for (int idx = 0; idx < pathsModel->rowCount(); idx++) {
0299         const QModelIndex midx = pathsModel->index(idx, 0);
0300         if (pathsModel->data(midx, Qt::DisplayRole) == ui->projectPaths->currentText()) {
0301             pathsModel->setData(midx, QVariant::fromValue(currentCompiler()), ProjectPathsModel::CompilerDataRole);
0302             break;
0303         }
0304     }
0305 }
0306 
0307 #include "moc_projectpathswidget.cpp"