File indexing completed on 2024-05-12 04:39:28
0001 /* 0002 SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org> 0003 SPDX-FileCopyrightText: 2007-2008 Aleix Pol <aleixpol@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "cmakepreferences.h" 0009 0010 #include <interfaces/icore.h> 0011 0012 #include <KLocalizedString> 0013 #include <KMessageBox> 0014 #include <KMessageBox_KDevCompat> 0015 #include <KJobWidgets> 0016 #include <KIO/DeleteJob> 0017 0018 #include <QFile> 0019 #include <QDir> 0020 #include <QHeaderView> 0021 #include <QComboBox> 0022 0023 #include "ui_cmakebuildsettings.h" 0024 #include "cmakecachedelegate.h" 0025 #include "cmakebuilddirchooser.h" 0026 #include "cmakebuilderconfig.h" 0027 #include <debug.h> 0028 #include <cmakeutils.h> 0029 #include <interfaces/iproject.h> 0030 #include <project/interfaces/ibuildsystemmanager.h> 0031 #include <project/interfaces/iprojectbuilder.h> 0032 #include <interfaces/iruncontroller.h> 0033 0034 using namespace KDevelop; 0035 0036 CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent) 0037 : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr) 0038 { 0039 m_prefsUi = new Ui::CMakeBuildSettings; 0040 m_prefsUi->setupUi(this); 0041 0042 m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList)); 0043 m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection); 0044 m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true); 0045 m_prefsUi->cacheList->verticalHeader()->hide(); 0046 0047 // configure the extraArguments widget to span the advanced box width but not 0048 // expand the dialog to the width of the longest element in the argument history. 0049 // static_cast<QComboBox*> needed because KComboBox::minimumSizeHint() override by mistake made it protected 0050 // TODO KF6: remove cast, no longer needed 0051 m_prefsUi->extraArguments->setMinimumWidth(static_cast<QComboBox*>(m_prefsUi->extraArguments)->minimumSizeHint().width()); 0052 m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_prefsUi->extraArguments); 0053 0054 connect(m_prefsUi->buildDirs, QOverload<int>::of(&KComboBox::currentIndexChanged), 0055 this, &CMakePreferences::buildDirChanged); 0056 connect(m_prefsUi->showInternal, &QCheckBox::stateChanged, 0057 this, &CMakePreferences::showInternal); 0058 connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir); 0059 connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir); 0060 connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced); 0061 connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged, 0062 this, &CMakePreferences::changed); 0063 connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured, 0064 this, &CMakePreferences::changed); 0065 0066 connect(m_prefsUi->installationPrefix, &KUrlRequester::textChanged, 0067 this, &CMakePreferences::changed); 0068 connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, 0069 this, &CMakePreferences::changed); 0070 connect(m_prefsUi->extraArguments, &KComboBox::currentTextChanged, 0071 this, &CMakePreferences::changed); 0072 connect(m_prefsUi->extraArguments, &KComboBox::editTextChanged, 0073 this, &CMakePreferences::changed); 0074 connect(m_prefsUi->cMakeExecutable, &KUrlRequester::textChanged, 0075 this, &CMakePreferences::changed); 0076 0077 showInternal(m_prefsUi->showInternal->checkState()); 0078 m_subprojFolder = Path(options.projectTempFile).parent(); 0079 0080 qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile; 0081 // foreach(const QVariant &v, args) 0082 // { 0083 // qCDebug(CMAKE) << "arg: " << v.toString(); 0084 // } 0085 0086 m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment); 0087 0088 m_prefsUi->showAdvanced->setChecked(false); 0089 showAdvanced(false); 0090 reset(); // load the initial values 0091 } 0092 0093 CMakePreferences::~CMakePreferences() 0094 { 0095 CMake::removeOverrideBuildDirIndex(m_project); 0096 delete m_extraArgumentsHistory; 0097 delete m_prefsUi; 0098 } 0099 0100 void CMakePreferences::initAdvanced() 0101 { 0102 m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); 0103 m_prefsUi->installationPrefix->setText(CMake::currentInstallDir(m_project).toLocalFile()); 0104 m_prefsUi->installationPrefix->setMode(KFile::Directory); 0105 setBuildType(CMake::currentBuildType(m_project)); 0106 m_prefsUi->extraArguments->setEditText(CMake::currentExtraArguments(m_project)); 0107 m_prefsUi->cMakeExecutable->setText(CMake::currentCMakeExecutable(m_project).toLocalFile()); 0108 } 0109 0110 void CMakePreferences::setBuildType(const QString& buildType) 0111 { 0112 if (m_prefsUi->buildType->currentText() == buildType) 0113 return; 0114 0115 if (m_prefsUi->buildType->findText(buildType) == -1) { 0116 m_prefsUi->buildType->addItem(buildType); 0117 } 0118 m_prefsUi->buildType->setCurrentIndex(m_prefsUi->buildType->findText(buildType)); 0119 } 0120 0121 void CMakePreferences::reset() 0122 { 0123 qCDebug(CMAKE) << "********loading"; 0124 m_prefsUi->buildDirs->clear(); 0125 m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) ); 0126 CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it 0127 m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) ); 0128 0129 initAdvanced(); 0130 0131 m_srcFolder = m_project->path(); 0132 0133 m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0); 0134 // QString cmDir=group.readEntry("CMakeDirectory"); 0135 // m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir)); 0136 // qCDebug(CMAKE) << "cmakedir" << cmDir; 0137 } 0138 0139 void CMakePreferences::apply() 0140 { 0141 qCDebug(CMAKE) << "*******saving"; 0142 0143 // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir(). 0144 // We won't rewrite it here based on the data from m_prefsUi->buildDirs. 0145 CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection 0146 int savedBuildDir = CMake::currentBuildDirIndex(m_project); 0147 if (savedBuildDir < 0) { 0148 // no build directory exists: skip any writing to config file as well as configuring 0149 return; 0150 } 0151 0152 CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() ); 0153 0154 CMake::setCurrentInstallDir( m_project, Path(m_prefsUi->installationPrefix->text()) ); 0155 const QString buildType = m_prefsUi->buildType->currentText(); 0156 CMake::setCurrentBuildType(m_project, buildType); 0157 CMake::setCurrentExtraArguments( m_project, m_prefsUi->extraArguments->currentText() ); 0158 CMake::setCurrentCMakeExecutable( m_project, Path(m_prefsUi->cMakeExecutable->text()) ); 0159 0160 qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project); 0161 qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project); 0162 qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project); 0163 qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project); 0164 qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); 0165 qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project); 0166 0167 //We run cmake on the builddir to generate it 0168 configure(); 0169 } 0170 0171 void CMakePreferences::defaults() 0172 { 0173 // do nothing 0174 } 0175 0176 void CMakePreferences::configureCacheView() 0177 { 0178 // Sets up the cache view after model re-creation/reset. 0179 // Emits changed(false) because model re-creation probably means 0180 // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want. 0181 m_prefsUi->cacheList->setModel(m_currentModel); 0182 m_prefsUi->cacheList->hideColumn(1); 0183 m_prefsUi->cacheList->hideColumn(3); 0184 m_prefsUi->cacheList->hideColumn(4); 0185 m_prefsUi->cacheList->hideColumn(5); 0186 m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200); 0187 0188 if( m_currentModel ) { 0189 m_prefsUi->cacheList->setEnabled( true ); 0190 const auto persistentIndices = m_currentModel->persistentIndices(); 0191 for (const QModelIndex& idx : persistentIndices) { 0192 m_prefsUi->cacheList->openPersistentEditor(idx); 0193 } 0194 } else { 0195 m_prefsUi->cacheList->setEnabled( false ); 0196 } 0197 0198 showInternal(m_prefsUi->showInternal->checkState()); 0199 } 0200 0201 void CMakePreferences::updateCache(const Path &newBuildDir) 0202 { 0203 const Path file = newBuildDir.isValid() ? Path(newBuildDir, QStringLiteral("CMakeCache.txt")) : Path(); 0204 if(QFile::exists(file.toLocalFile())) 0205 { 0206 if (m_currentModel) { 0207 m_currentModel->deleteLater(); 0208 } 0209 m_currentModel = new CMakeCacheModel(this, file); 0210 0211 configureCacheView(); 0212 connect(m_currentModel, &CMakeCacheModel::itemChanged, 0213 this, &CMakePreferences::cacheEdited); 0214 connect(m_currentModel, &CMakeCacheModel::modelReset, 0215 this, &CMakePreferences::configureCacheView); 0216 connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, 0217 this, &CMakePreferences::listSelectionChanged); 0218 connect(m_currentModel, &CMakeCacheModel::valueChanged, this, 0219 [this](const QString& name, const QString& value) { 0220 if (name == QLatin1String("CMAKE_BUILD_TYPE")) { 0221 setBuildType(value); 0222 } 0223 }); 0224 connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, m_currentModel, [this](const QString& value) { 0225 if (!m_currentModel) 0226 return; 0227 const auto items = m_currentModel->findItems(QStringLiteral("CMAKE_BUILD_TYPE")); 0228 for (auto* item : items) { 0229 m_currentModel->setData(m_currentModel->index(item->row(), 2), value); 0230 } 0231 }); 0232 } 0233 else 0234 { 0235 disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr); 0236 if (m_currentModel) { 0237 m_currentModel->deleteLater(); 0238 m_currentModel = nullptr; 0239 } 0240 configureCacheView(); 0241 } 0242 0243 if( !m_currentModel ) 0244 emit changed(); 0245 } 0246 0247 void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& ) 0248 { 0249 qCDebug(CMAKE) << "item " << index << " selected"; 0250 QModelIndex idx = index.sibling(index.row(), 3); 0251 QModelIndex idxType = index.sibling(index.row(), 1); 0252 QString comment=QStringLiteral("%1. %2") 0253 .arg(m_currentModel->itemFromIndex(idxType)->text(), 0254 m_currentModel->itemFromIndex(idx)->text()); 0255 m_prefsUi->commentText->setText(comment); 0256 } 0257 0258 void CMakePreferences::showInternal(int state) 0259 { 0260 if(!m_currentModel) 0261 return; 0262 0263 bool showAdv=(state == Qt::Checked); 0264 for(int i=0; i<m_currentModel->rowCount(); i++) 0265 { 0266 bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i)); 0267 m_prefsUi->cacheList->setRowHidden(i, hidden); 0268 } 0269 } 0270 0271 void CMakePreferences::buildDirChanged(int index) 0272 { 0273 CMake::setOverrideBuildDirIndex( m_project, index ); 0274 const Path buildDir = CMake::currentBuildDir(m_project); 0275 initAdvanced(); 0276 updateCache(buildDir); 0277 qCDebug(CMAKE) << "builddir Changed" << buildDir; 0278 emit changed(); 0279 } 0280 0281 void CMakePreferences::cacheUpdated() 0282 { 0283 const Path buildDir = CMake::currentBuildDir(m_project); 0284 updateCache(buildDir); 0285 qCDebug(CMAKE) << "cache updated for" << buildDir; 0286 } 0287 0288 void CMakePreferences::createBuildDir() 0289 { 0290 CMakeBuildDirChooser bdCreator; 0291 bdCreator.setProject( m_project ); 0292 0293 // NOTE: (on removing the trailing slashes) 0294 // Generally, we have no clue about how shall a trailing slash look in the current system. 0295 // Moreover, the slash may be a part of the filename. 0296 // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed paths? 0297 QStringList used = CMake::allBuildDirs( m_project ); 0298 bdCreator.setAlreadyUsed(used); 0299 bdCreator.setCMakeExecutable(Path(CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile())); 0300 0301 if(bdCreator.exec()) 0302 { 0303 int addedBuildDirIndex = m_prefsUi->buildDirs->count(); 0304 0305 // Initialize the kconfig items with the values from the dialog, this ensures the settings 0306 // end up in the config file once the changes are saved 0307 qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; 0308 qCDebug(CMAKE) << "adding to cmake config: builddir path " << bdCreator.buildFolder(); 0309 qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix(); 0310 qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments(); 0311 qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType(); 0312 qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bdCreator.cmakeExecutable(); 0313 qCDebug(CMAKE) << "adding to cmake config: environment empty"; 0314 CMake::setOverrideBuildDirIndex( m_project, addedBuildDirIndex ); 0315 CMake::setBuildDirCount( m_project, addedBuildDirIndex + 1 ); 0316 CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() ); 0317 CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() ); 0318 CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() ); 0319 CMake::setCurrentBuildType( m_project, bdCreator.buildType() ); 0320 CMake::setCurrentCMakeExecutable(m_project, bdCreator.cmakeExecutable()); 0321 CMake::setCurrentEnvironment( m_project, QString() ); 0322 0323 QString newbuilddir = bdCreator.buildFolder().toLocalFile(); 0324 m_prefsUi->buildDirs->addItem( newbuilddir ); 0325 m_prefsUi->buildDirs->setCurrentIndex( addedBuildDirIndex ); 0326 m_prefsUi->removeBuildDir->setEnabled( true ); 0327 0328 qCDebug(CMAKE) << "Emitting changed signal for cmake kcm"; 0329 emit changed(); 0330 } 0331 //TODO: Save it for next runs 0332 } 0333 0334 void CMakePreferences::removeBuildDir() 0335 { 0336 int curr=m_prefsUi->buildDirs->currentIndex(); 0337 if(curr < 0) 0338 return; 0339 0340 Path removedPath = CMake::currentBuildDir( m_project ); 0341 QString removed = removedPath.toLocalFile(); 0342 if(QDir(removed).exists()) 0343 { 0344 int ret = KMessageBox::warningTwoActions( 0345 this, 0346 i18n("The %1 directory is about to be removed in KDevelop's list.\n" 0347 "Do you want KDevelop to delete it in the file system as well?", 0348 removed), 0349 {}, KStandardGuiItem::del(), 0350 KGuiItem(i18nc("@action:button", "Do Not Delete"), QStringLiteral("dialog-cancel"))); 0351 if (ret == KMessageBox::PrimaryAction) { 0352 auto deleteJob = KIO::del(removedPath.toUrl()); 0353 KJobWidgets::setWindow(deleteJob, this); 0354 if (!deleteJob->exec()) 0355 KMessageBox::error(this, i18n("Could not remove: %1", removed)); 0356 } 0357 } 0358 0359 qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr; 0360 qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath; 0361 qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project ); 0362 qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project ); 0363 qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project ); 0364 qCDebug(CMAKE) << "removing from cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); 0365 qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project ); 0366 0367 0368 CMake::removeBuildDirConfig(m_project); 0369 m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged() 0370 if(m_prefsUi->buildDirs->count()==0) 0371 m_prefsUi->removeBuildDir->setEnabled(false); 0372 0373 emit changed(); 0374 } 0375 0376 void CMakePreferences::configure() 0377 { 0378 IProjectBuilder *b=m_project->buildSystemManager()->builder(); 0379 KJob* job=b->configure(m_project); 0380 if( m_currentModel ) { 0381 QVariantMap map = m_currentModel->changedValues(); 0382 job->setProperty("extraCMakeCacheValues", map); 0383 connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset); 0384 } else { 0385 connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated); 0386 } 0387 0388 connect(job, &KJob::finished, m_project, &IProject::reloadModel); 0389 ICore::self()->runController()->registerJob(job); 0390 } 0391 0392 void CMakePreferences::showAdvanced(bool v) 0393 { 0394 qCDebug(CMAKE) << "toggle pressed: " << v; 0395 m_prefsUi->advancedBox->setHidden(!v); 0396 } 0397 0398 0399 QString CMakePreferences::name() const 0400 { 0401 return i18nc("@title:tab", "CMake"); 0402 } 0403 0404 QString CMakePreferences::fullName() const 0405 { 0406 return i18nc("@title:tab", "Configure CMake Settings"); 0407 } 0408 0409 QIcon CMakePreferences::icon() const 0410 { 0411 return QIcon::fromTheme(QStringLiteral("cmake")); 0412 } 0413 0414 #include "moc_cmakepreferences.cpp"