| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "CMakeSetupDialog.h" | |
| #include <cm/memory> | |
| #include <QCloseEvent> | |
| #include <QCoreApplication> | |
| #include <QDesktopServices> | |
| #include <QDialogButtonBox> | |
| #include <QDragEnterEvent> | |
| #include <QFileDialog> | |
| #include <QInputDialog> | |
| #include <QKeySequence> | |
| #include <QMenu> | |
| #include <QMenuBar> | |
| #include <QMessageBox> | |
| #include <QMimeData> | |
| #include <QProcessEnvironment> | |
| #include <QProgressBar> | |
| #include <QSettings> | |
| #include <QShortcut> | |
| #include <QStatusBar> | |
| #include <QString> | |
| #include <QUrl> | |
| #include <QVector> | |
| #ifdef QT_WINEXTRAS | |
| # include <QWinTaskbarButton> | |
| # include <QWinTaskbarProgress> | |
| #endif | |
| #include "cmSystemTools.h" | |
| #include "cmVersion.h" | |
| #include "AddCacheEntry.h" | |
| #include "EnvironmentDialog.h" | |
| #include "FirstConfigure.h" | |
| #include "QCMake.h" | |
| #include "QCMakeCacheView.h" | |
| #include "QCMakeSizeType.h" | |
| #include "RegexExplorer.h" | |
| #include "WarningMessagesDialog.h" | |
| void OpenReferenceManual(QString const& filename) | |
| { | |
| QString urlFormat("https://cmake.org/cmake/help/v%1.%2/"); | |
| QUrl url(urlFormat.arg(QString::number(cmVersion::GetMajorVersion()), | |
| QString::number(cmVersion::GetMinorVersion()))); | |
| if (!cmSystemTools::GetHTMLDoc().empty()) { | |
| url = QUrl::fromLocalFile( | |
| QDir(QString::fromStdString(cmSystemTools::GetHTMLDoc())) | |
| .filePath(filename)); | |
| } | |
| QDesktopServices::openUrl(url); | |
| } | |
| namespace { | |
| QString const PRESETS_DISABLED_TOOLTIP = | |
| "This option is disabled because there are no available presets in " | |
| "CMakePresets.json or CMakeUserPresets.json."; | |
| } | |
| QCMakeThread::QCMakeThread(QObject* p) | |
| : QThread(p) | |
| { | |
| } | |
| QCMake* QCMakeThread::cmakeInstance() const | |
| { | |
| return this->CMakeInstance.get(); | |
| } | |
| void QCMakeThread::run() | |
| { | |
| this->CMakeInstance = cm::make_unique<QCMake>(); | |
| // emit that this cmake thread is ready for use | |
| emit this->cmakeInitialized(); | |
| this->exec(); | |
| this->CMakeInstance.reset(); | |
| } | |
| CMakeSetupDialog::CMakeSetupDialog() | |
| : ExitAfterGenerate(true) | |
| , CacheModified(false) | |
| , ConfigureNeeded(true) | |
| , CurrentState(Interrupting) | |
| { | |
| QString title = QString(tr("CMake %1")); | |
| title = title.arg(cmVersion::GetCMakeVersion()); | |
| this->setWindowTitle(title); | |
| // create the GUI | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| restoreGeometry(settings.value("geometry").toByteArray()); | |
| restoreState(settings.value("windowState").toByteArray()); | |
| this->AddVariableNames = | |
| settings.value("AddVariableNames", QStringList("CMAKE_INSTALL_PREFIX")) | |
| .toStringList(); | |
| this->AddVariableTypes = | |
| settings.value("AddVariableTypes", QStringList("PATH")).toStringList(); | |
| QWidget* cont = new QWidget(this); | |
| this->setupUi(cont); | |
| this->Splitter->setStretchFactor(0, 3); | |
| this->Splitter->setStretchFactor(1, 1); | |
| this->setCentralWidget(cont); | |
| this->ProgressBar->reset(); | |
| this->RemoveEntry->setEnabled(false); | |
| this->AddEntry->setEnabled(false); | |
| this->Preset->setStatusTip(PRESETS_DISABLED_TOOLTIP); | |
| QByteArray p = settings.value("SplitterSizes").toByteArray(); | |
| this->Splitter->restoreState(p); | |
| bool groupView = settings.value("GroupView", false).toBool(); | |
| this->setGroupedView(groupView); | |
| this->groupedCheck->setCheckState(groupView ? Qt::Checked : Qt::Unchecked); | |
| bool advancedView = settings.value("AdvancedView", false).toBool(); | |
| this->setAdvancedView(advancedView); | |
| this->advancedCheck->setCheckState(advancedView ? Qt::Checked | |
| : Qt::Unchecked); | |
| QMenu* FileMenu = this->menuBar()->addMenu(tr("&File")); | |
| this->ReloadCacheAction = FileMenu->addAction(tr("&Reload Cache")); | |
| QObject::connect(this->ReloadCacheAction, &QAction::triggered, this, | |
| &CMakeSetupDialog::doReloadCache); | |
| this->DeleteCacheAction = FileMenu->addAction(tr("&Delete Cache")); | |
| QObject::connect(this->DeleteCacheAction, &QAction::triggered, this, | |
| &CMakeSetupDialog::doDeleteCache); | |
| this->ExitAction = FileMenu->addAction(tr("E&xit")); | |
| QObject::connect(this->ExitAction, &QAction::triggered, this, | |
| &CMakeSetupDialog::close); | |
| this->ExitAction->setShortcut(QKeySequence::Quit); | |
| QMenu* ToolsMenu = this->menuBar()->addMenu(tr("&Tools")); | |
| this->ConfigureAction = ToolsMenu->addAction(tr("&Configure")); | |
| QObject::connect(this->ConfigureAction, &QAction::triggered, this, | |
| &CMakeSetupDialog::doConfigure); | |
| // prevent merging with Preferences menu item on macOS | |
| this->ConfigureAction->setMenuRole(QAction::NoRole); | |
| this->GenerateAction = ToolsMenu->addAction(tr("&Generate")); | |
| QObject::connect(this->GenerateAction, &QAction::triggered, this, | |
| &CMakeSetupDialog::doGenerate); | |
| auto* a = ToolsMenu->addAction(tr("&Show My Changes")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::showUserChanges); | |
| #if defined(Q_WS_MAC) || defined(Q_OS_MAC) | |
| this->InstallForCommandLineAction = | |
| ToolsMenu->addAction(tr("&How to Install For Command Line Use")); | |
| QObject::connect(this->InstallForCommandLineAction, &QAction::triggered, | |
| this, &CMakeSetupDialog::doInstallForCommandLine); | |
| #endif | |
| ToolsMenu->addSeparator(); | |
| a = ToolsMenu->addAction(tr("Regular Expression Explorer...")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doRegexExplorerDialog); | |
| ToolsMenu->addSeparator(); | |
| a = ToolsMenu->addAction(tr("&Find in Output...")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindDialog); | |
| a->setShortcut(QKeySequence::Find); | |
| a = ToolsMenu->addAction(tr("Find Next")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindNext); | |
| a->setShortcut(QKeySequence::FindNext); | |
| a = ToolsMenu->addAction(tr("Find Previous")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindPrev); | |
| a->setShortcut(QKeySequence::FindPrevious); | |
| a = ToolsMenu->addAction(tr("Goto Next Error")); // in Visual Studio | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputErrorNext); | |
| a->setShortcut(QKeySequence(Qt::Key_F8)); | |
| auto* s = new QShortcut(this); | |
| #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) | |
| s->setKey(QKeySequence(Qt::CTRL + Qt::Key_Period)); | |
| #else | |
| s->setKey(QKeySequence(Qt::CTRL | Qt::Key_Period)); | |
| #endif | |
| QObject::connect(s, &QShortcut::activated, this, | |
| &CMakeSetupDialog::doOutputErrorNext); // in Eclipse | |
| QMenu* OptionsMenu = this->menuBar()->addMenu(tr("&Options")); | |
| a = OptionsMenu->addAction(tr("Warning Messages...")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doWarningMessagesDialog); | |
| QAction* debugAction = OptionsMenu->addAction(tr("&Debug Output")); | |
| debugAction->setCheckable(true); | |
| QObject::connect(debugAction, &QAction::toggled, this, | |
| &CMakeSetupDialog::setDebugOutput); | |
| OptionsMenu->addSeparator(); | |
| a = OptionsMenu->addAction(tr("&Expand Grouped Entries")); | |
| QObject::connect(a, &QAction::triggered, this->CacheValues, | |
| &QCMakeCacheView::expandAll); | |
| a = OptionsMenu->addAction(tr("&Collapse Grouped Entries")); | |
| QObject::connect(a, &QAction::triggered, this->CacheValues, | |
| &QCMakeCacheView::collapseAll); | |
| QMenu* HelpMenu = this->menuBar()->addMenu(tr("&Help")); | |
| a = HelpMenu->addAction(tr("Help")); | |
| QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doHelp); | |
| a->setShortcut(QKeySequence::HelpContents); | |
| a = HelpMenu->addAction(tr("CMake Reference Manual")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| [] { OpenReferenceManual("index.html"); }); | |
| a = HelpMenu->addAction(tr("About")); | |
| QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doAbout); | |
| this->setAcceptDrops(true); | |
| // get the saved binary directories | |
| QStringList buildPaths = this->loadBuildPaths(); | |
| this->BinaryDirectory->addItems(buildPaths); | |
| this->BinaryDirectory->setCompleter(new QCMakeFileCompleter(this, true)); | |
| this->SourceDirectory->setCompleter(new QCMakeFileCompleter(this, true)); | |
| // fixed pitch font in output window | |
| QFont outputFont("Courier New"); | |
| outputFont.setStyleHint(QFont::Monospace); | |
| this->Output->setFont(outputFont); | |
| this->ErrorFormat.setForeground(QBrush(Qt::red)); | |
| this->Output->setContextMenuPolicy(Qt::CustomContextMenu); | |
| connect(this->Output, &QTextEdit::customContextMenuRequested, this, | |
| &CMakeSetupDialog::doOutputContextMenu); | |
| // disable open project button | |
| this->OpenProjectButton->setDisabled(true); | |
| // start the cmake worker thread | |
| this->CMakeThread = new QCMakeThread(this); | |
| QObject::connect(this->CMakeThread, &QCMakeThread::cmakeInitialized, this, | |
| &CMakeSetupDialog::initialize, Qt::QueuedConnection); | |
| this->CMakeThread->start(); | |
| this->enterState(ReadyConfigure); | |
| ProgressOffset = 0.0; | |
| ProgressFactor = 1.0; | |
| } | |
| void CMakeSetupDialog::initialize() | |
| { | |
| // now the cmake worker thread is running, lets make our connections to it | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::propertiesChanged, this->CacheValues->cacheModel(), | |
| &QCMakeCacheModel::setProperties); | |
| QObject::connect(this->ConfigureButton, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::doConfigure); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::configureDone, | |
| this, &CMakeSetupDialog::exitLoop); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::generateDone, | |
| this, &CMakeSetupDialog::exitLoop); | |
| QObject::connect(this->GenerateButton, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::doGenerate); | |
| QObject::connect(this->OpenProjectButton, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::doOpenProject); | |
| QObject::connect(this->BrowseSourceDirectoryButton, | |
| &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::doSourceBrowse); | |
| QObject::connect(this->ReloadPresetsButton, &QAbstractButton::clicked, | |
| this->CMakeThread->cmakeInstance(), &QCMake::loadPresets); | |
| QObject::connect(this->BrowseBinaryDirectoryButton, | |
| &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::doBinaryBrowse); | |
| QObject::connect(this->BinaryDirectory, &QComboBox::editTextChanged, this, | |
| &CMakeSetupDialog::onBinaryDirectoryChanged); | |
| QObject::connect(this->SourceDirectory, &QLineEdit::textChanged, this, | |
| &CMakeSetupDialog::onSourceDirectoryChanged); | |
| QObject::connect(this->Preset, &QCMakePresetComboBox::presetChanged, this, | |
| &CMakeSetupDialog::onBuildPresetChanged); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::sourceDirChanged, this, | |
| &CMakeSetupDialog::updateSourceDirectory); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::binaryDirChanged, this, | |
| &CMakeSetupDialog::updateBinaryDirectory); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetsChanged, | |
| this, &CMakeSetupDialog::updatePresets); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetChanged, | |
| this, &CMakeSetupDialog::updatePreset); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetApplied, | |
| this, &CMakeSetupDialog::onPresetApplied); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::presetLoadError, this, | |
| &CMakeSetupDialog::showPresetLoadError); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::progressChanged, this, | |
| &CMakeSetupDialog::showProgress); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::errorMessage, | |
| this, &CMakeSetupDialog::error); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::outputMessage, | |
| this, &CMakeSetupDialog::message); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::openPossible, | |
| this->OpenProjectButton, &CMakeSetupDialog::setEnabled); | |
| QObject::connect(this->groupedCheck, &QCheckBox::toggled, this, | |
| &CMakeSetupDialog::setGroupedView); | |
| QObject::connect(this->advancedCheck, &QCheckBox::toggled, this, | |
| &CMakeSetupDialog::setAdvancedView); | |
| QObject::connect(this->Search, &QLineEdit::textChanged, this, | |
| &CMakeSetupDialog::setSearchFilter); | |
| QObject::connect(this->CMakeThread->cmakeInstance(), | |
| &QCMake::generatorChanged, this, | |
| &CMakeSetupDialog::updateGeneratorLabel); | |
| this->updateGeneratorLabel(QString()); | |
| QObject::connect(this->CacheValues->cacheModel(), | |
| &QCMakeCacheModel::dataChanged, this, | |
| &CMakeSetupDialog::setCacheModified); | |
| QObject::connect(this->CacheValues->selectionModel(), | |
| &QItemSelectionModel::selectionChanged, this, | |
| &CMakeSetupDialog::selectionChanged); | |
| QObject::connect(this->RemoveEntry, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::removeSelectedCacheEntries); | |
| QObject::connect(this->AddEntry, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::addCacheEntry); | |
| QObject::connect(this->Environment, &QAbstractButton::clicked, this, | |
| &CMakeSetupDialog::editEnvironment); | |
| if (!this->SourceDirectory->text().isEmpty() && | |
| !this->DeferredPreset.isNull()) { | |
| this->onSourceDirectoryChanged(this->SourceDirectory->text()); | |
| if (!this->BinaryDirectory->lineEdit()->text().isEmpty()) { | |
| this->onBinaryDirectoryChanged( | |
| this->BinaryDirectory->lineEdit()->text()); | |
| } | |
| } else if (!this->SourceDirectory->text().isEmpty() || | |
| !this->BinaryDirectory->lineEdit()->text().isEmpty()) { | |
| if (this->SourceDirectory->text().isEmpty() && | |
| !cmSystemTools::FileIsFullPath( | |
| this->BinaryDirectory->lineEdit()->text().toStdString())) { | |
| // If the binary directory is relative, load the previous source path | |
| // from the config | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| QString srcDir = settings.value(QString("WhereSource")).toString(); | |
| this->SourceDirectory->blockSignals(true); | |
| this->SourceDirectory->setText(srcDir); | |
| this->SourceDirectory->blockSignals(false); | |
| } | |
| this->onSourceDirectoryChanged(this->SourceDirectory->text()); | |
| this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text()); | |
| } else { | |
| this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text()); | |
| } | |
| #ifdef QT_WINEXTRAS | |
| this->TaskbarButton = new QWinTaskbarButton(this); | |
| this->TaskbarButton->setWindow(this->windowHandle()); | |
| #endif | |
| } | |
| CMakeSetupDialog::~CMakeSetupDialog() | |
| { | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| settings.setValue("windowState", QVariant(saveState())); | |
| settings.setValue("geometry", QVariant(saveGeometry())); | |
| settings.setValue("SplitterSizes", this->Splitter->saveState()); | |
| // wait for thread to stop | |
| this->CMakeThread->quit(); | |
| this->CMakeThread->wait(); | |
| } | |
| bool CMakeSetupDialog::prepareConfigure() | |
| { | |
| // make sure build directory exists | |
| QString bindir = this->CMakeThread->cmakeInstance()->binaryDirectory(); | |
| QDir dir(bindir); | |
| if (!dir.exists()) { | |
| QString msg = tr("Build directory does not exist, " | |
| "should I create it?\n\n" | |
| "Directory: "); | |
| msg += bindir; | |
| QString title = tr("Create Directory"); | |
| QMessageBox::StandardButton btn; | |
| btn = QMessageBox::information(this, title, msg, | |
| QMessageBox::Yes | QMessageBox::No); | |
| if (btn == QMessageBox::No) { | |
| return false; | |
| } | |
| if (!dir.mkpath(".")) { | |
| QMessageBox::information( | |
| this, tr("Create Directory Failed"), | |
| QString(tr("Failed to create directory %1")).arg(dir.path()), | |
| QMessageBox::Ok); | |
| return false; | |
| } | |
| } | |
| // if no generator, prompt for it and other setup stuff | |
| if (this->CMakeThread->cmakeInstance()->generator().isEmpty()) { | |
| if (!this->setupFirstConfigure()) { | |
| return false; | |
| } | |
| } | |
| // remember paths | |
| this->addBinaryPath( | |
| this->CMakeThread->cmakeInstance()->relativeBinaryDirectory()); | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| settings.setValue("WhereSource", | |
| this->CMakeThread->cmakeInstance()->sourceDirectory()); | |
| return true; | |
| } | |
| void CMakeSetupDialog::exitLoop(int err) | |
| { | |
| this->LocalLoop.exit(err); | |
| } | |
| void CMakeSetupDialog::doConfigure() | |
| { | |
| if (this->CurrentState == Configuring) { | |
| // stop configure | |
| doInterrupt(); | |
| return; | |
| } | |
| // Preset still applying: its cache values aren't in the model yet. | |
| if (this->PresetApplicationPending) { | |
| return; | |
| } | |
| if (!prepareConfigure()) { | |
| return; | |
| } | |
| this->enterState(Configuring); | |
| bool ret = doConfigureInternal(); | |
| if (ret) { | |
| this->ConfigureNeeded = false; | |
| } | |
| if (ret && !this->CacheValues->cacheModel()->newPropertyCount()) { | |
| this->enterState(ReadyGenerate); | |
| } else { | |
| this->enterState(ReadyConfigure); | |
| this->CacheValues->scrollToTop(); | |
| } | |
| this->ProgressBar->reset(); | |
| #ifdef QT_WINEXTRAS | |
| this->TaskbarButton->progress()->reset(); | |
| #endif | |
| } | |
| bool CMakeSetupDialog::doConfigureInternal() | |
| { | |
| this->Output->clear(); | |
| this->CacheValues->selectionModel()->clear(); | |
| QMetaObject::invokeMethod( | |
| this->CMakeThread->cmakeInstance(), "setProperties", Qt::QueuedConnection, | |
| Q_ARG(QCMakePropertyList, this->CacheValues->cacheModel()->properties())); | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "configure", | |
| Qt::QueuedConnection); | |
| int err = this->LocalLoop.exec(); | |
| if (err != 0) { | |
| QMessageBox::critical( | |
| this, tr("Error"), | |
| tr("Error in configuration process, project files may be invalid"), | |
| QMessageBox::Ok); | |
| } | |
| return 0 == err; | |
| } | |
| void CMakeSetupDialog::doInstallForCommandLine() | |
| { | |
| QString title = tr("How to Install For Command Line Use"); | |
| QString msg = tr("One may add CMake to the PATH:\n" | |
| "\n" | |
| " PATH=\"%1\":\"$PATH\"\n" | |
| "\n" | |
| "Or, to install symlinks to '/usr/local/bin', run:\n" | |
| "\n" | |
| " sudo \"%2\" --install\n" | |
| "\n" | |
| "Or, to install symlinks to another directory, run:\n" | |
| "\n" | |
| " sudo \"%3\" --install=/path/to/bin\n"); | |
| msg = msg.arg( | |
| cmSystemTools::GetFilenamePath(cmSystemTools::GetCMakeCommand()).c_str()); | |
| msg = msg.arg(cmSystemTools::GetCMakeGUICommand().c_str()); | |
| msg = msg.arg(cmSystemTools::GetCMakeGUICommand().c_str()); | |
| QDialog dialog; | |
| dialog.setWindowTitle(title); | |
| QVBoxLayout* l = new QVBoxLayout(&dialog); | |
| QLabel* lab = new QLabel(&dialog); | |
| l->addWidget(lab); | |
| lab->setText(msg); | |
| lab->setWordWrap(false); | |
| lab->setTextInteractionFlags(Qt::TextSelectableByMouse); | |
| QDialogButtonBox* btns = | |
| new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog); | |
| QObject::connect(btns, &QDialogButtonBox::accepted, &dialog, | |
| &QDialog::accept); | |
| l->addWidget(btns); | |
| dialog.exec(); | |
| } | |
| bool CMakeSetupDialog::doGenerateInternal() | |
| { | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "generate", | |
| Qt::QueuedConnection); | |
| int err = this->LocalLoop.exec(); | |
| if (err != 0) { | |
| QMessageBox::critical( | |
| this, tr("Error"), | |
| tr("Error in generation process, project files may be invalid"), | |
| QMessageBox::Ok); | |
| } | |
| return 0 == err; | |
| } | |
| void CMakeSetupDialog::doGenerate() | |
| { | |
| if (this->CurrentState == Generating) { | |
| // stop generate | |
| doInterrupt(); | |
| return; | |
| } | |
| // Preset still applying: prepareConfigure() would read worker state that | |
| // applyPreset() is still mutating. | |
| if (this->PresetApplicationPending) { | |
| return; | |
| } | |
| // see if we need to configure | |
| // we'll need to configure if: | |
| // the configure step hasn't been done yet | |
| // generate was the last step done | |
| if (this->ConfigureNeeded) { | |
| if (!prepareConfigure()) { | |
| return; | |
| } | |
| } | |
| this->enterState(Generating); | |
| bool config_passed = true; | |
| if (this->ConfigureNeeded) { | |
| this->CacheValues->cacheModel()->setShowNewProperties(false); | |
| this->ProgressFactor = 0.5; | |
| config_passed = doConfigureInternal(); | |
| this->ProgressOffset = 0.5; | |
| } | |
| if (config_passed) { | |
| doGenerateInternal(); | |
| } | |
| this->ProgressOffset = 0.0; | |
| this->ProgressFactor = 1.0; | |
| this->CacheValues->cacheModel()->setShowNewProperties(true); | |
| this->enterState(ReadyConfigure); | |
| this->ProgressBar->reset(); | |
| #ifdef QT_WINEXTRAS | |
| this->TaskbarButton->progress()->reset(); | |
| #endif | |
| this->ConfigureNeeded = true; | |
| } | |
| void CMakeSetupDialog::doOpenProject() | |
| { | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "open", | |
| Qt::QueuedConnection); | |
| } | |
| void CMakeSetupDialog::closeEvent(QCloseEvent* e) | |
| { | |
| // prompt for close if there are unsaved changes, and we're not busy | |
| if (this->CacheModified) { | |
| QString msg = tr("You have changed options but not rebuilt, " | |
| "are you sure you want to exit?"); | |
| QString title = tr("Confirm Exit"); | |
| QMessageBox::StandardButton btn; | |
| btn = QMessageBox::critical(this, title, msg, | |
| QMessageBox::Yes | QMessageBox::No); | |
| if (btn == QMessageBox::No) { | |
| e->ignore(); | |
| } | |
| } | |
| // don't close if we're busy, unless the user really wants to | |
| if (this->CurrentState == Configuring) { | |
| QString msg = | |
| tr("You are in the middle of a Configure.\n" | |
| "If you Exit now the configure information will be lost.\n" | |
| "Are you sure you want to Exit?"); | |
| QString title = tr("Confirm Exit"); | |
| QMessageBox::StandardButton btn; | |
| btn = QMessageBox::critical(this, title, msg, | |
| QMessageBox::Yes | QMessageBox::No); | |
| if (btn == QMessageBox::No) { | |
| e->ignore(); | |
| } else { | |
| this->doInterrupt(); | |
| } | |
| } | |
| // let the generate finish | |
| if (this->CurrentState == Generating) { | |
| e->ignore(); | |
| } | |
| } | |
| void CMakeSetupDialog::doHelp() | |
| { | |
| QString msg = tr( | |
| "CMake is used to configure and generate build files for " | |
| "software projects. The basic steps for configuring a project are as " | |
| "follows:\r\n\r\n1. Select the source directory for the project. This " | |
| "should " | |
| "contain the CMakeLists.txt files for the project.\r\n\r\n2. Select the " | |
| "build " | |
| "directory for the project. This is the directory where the project " | |
| "will be " | |
| "built. It can be the same or a different directory than the source " | |
| "directory. For easy clean up, a separate build directory is " | |
| "recommended. " | |
| "CMake will create the directory if it does not exist.\r\n\r\n3. Once the " | |
| "source and binary directories are selected, it is time to press the " | |
| "Configure button. This will cause CMake to read all of the input files " | |
| "and " | |
| "discover all the variables used by the project. The first time a " | |
| "variable " | |
| "is displayed it will be in Red. Users should inspect red variables " | |
| "making " | |
| "sure the values are correct. For some projects the Configure process " | |
| "can " | |
| "be iterative, so continue to press the Configure button until there are " | |
| "no " | |
| "longer red entries.\r\n\r\n4. Once there are no longer red entries, you " | |
| "should click the Generate button. This will write the build files to " | |
| "the build " | |
| "directory."); | |
| QDialog dialog; | |
| QFontMetrics met(this->font()); | |
| #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) | |
| int msgWidth = met.horizontalAdvance(msg); | |
| #else | |
| int msgWidth = met.width(msg); | |
| #endif | |
| dialog.setMinimumSize(msgWidth / 15, 20); | |
| dialog.setWindowTitle(tr("Help")); | |
| QVBoxLayout* l = new QVBoxLayout(&dialog); | |
| QLabel* lab = new QLabel(&dialog); | |
| lab->setText(msg); | |
| lab->setWordWrap(true); | |
| QDialogButtonBox* btns = | |
| new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog); | |
| QObject::connect(btns, &QDialogButtonBox::accepted, &dialog, | |
| &QDialog::accept); | |
| l->addWidget(lab); | |
| l->addWidget(btns); | |
| dialog.exec(); | |
| } | |
| void CMakeSetupDialog::doInterrupt() | |
| { | |
| this->enterState(Interrupting); | |
| this->CMakeThread->cmakeInstance()->interrupt(); | |
| } | |
| void CMakeSetupDialog::doSourceBrowse() | |
| { | |
| QString dir = QFileDialog::getExistingDirectory( | |
| this, tr("Enter Path to Source"), this->SourceDirectory->text(), | |
| QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); | |
| if (!dir.isEmpty()) { | |
| this->setSourceDirectory(dir); | |
| } | |
| } | |
| void CMakeSetupDialog::updateSourceDirectory(QString const& dir) | |
| { | |
| if (this->SourceDirectory->text() != dir) { | |
| this->SourceDirectory->blockSignals(true); | |
| this->SourceDirectory->setText(dir); | |
| this->SourceDirectory->blockSignals(false); | |
| } | |
| } | |
| void CMakeSetupDialog::updateBinaryDirectory(QString const& dir) | |
| { | |
| if (this->BinaryDirectory->currentText() != dir) { | |
| this->BinaryDirectory->blockSignals(true); | |
| this->BinaryDirectory->setEditText(dir); | |
| this->BinaryDirectory->blockSignals(false); | |
| } | |
| } | |
| void CMakeSetupDialog::updatePresets(QVector<QCMakePreset> const& presets) | |
| { | |
| if (this->Preset->presets() != presets) { | |
| this->Preset->blockSignals(true); | |
| this->Preset->setPresets(presets); | |
| this->Preset->blockSignals(false); | |
| } | |
| this->Preset->setDisabled(presets.isEmpty()); | |
| this->Preset->setToolTip(presets.isEmpty() ? PRESETS_DISABLED_TOOLTIP : ""); | |
| if (!this->DeferredPreset.isNull()) { | |
| QString const deferred = this->DeferredPreset; | |
| this->DeferredPreset = QString{}; | |
| // An available preset selection submits a real request and keeps the gate | |
| // engaged; an unavailable one applies nothing, so release the gate here. | |
| this->Preset->setPresetName(deferred); | |
| if (this->Preset->presetName() != deferred) { | |
| this->PresetApplicationPending = false; | |
| this->updateCommandState(); | |
| } | |
| } | |
| } | |
| void CMakeSetupDialog::onPresetApplied(quint64 requestId, QString const& name, | |
| QCMakePropertyList const& properties) | |
| { | |
| // Latest request wins; ignore a superseded completion. | |
| if (requestId != this->LatestPresetRequestId) { | |
| return; | |
| } | |
| this->CacheValues->cacheModel()->setProperties(properties); | |
| // Reconcile in case the combo drifted from what actually applied. | |
| if (this->Preset->presetName() != name) { | |
| this->Preset->blockSignals(true); | |
| this->Preset->setPresetName(name); | |
| this->Preset->blockSignals(false); | |
| } | |
| this->PresetApplicationPending = false; | |
| this->updateCommandState(); | |
| } | |
| void CMakeSetupDialog::updatePreset(QString const& name) | |
| { | |
| if (this->Preset->presetName() != name) { | |
| this->Preset->blockSignals(true); | |
| this->Preset->setPresetName(name); | |
| this->Preset->blockSignals(false); | |
| } | |
| } | |
| void CMakeSetupDialog::showPresetLoadError(QString const& dir, | |
| QString const& message) | |
| { | |
| QMessageBox::warning( | |
| this, "Error Reading CMake Presets", | |
| QString("Could not read presets from %1: %2").arg(dir, message)); | |
| } | |
| void CMakeSetupDialog::doBinaryBrowse() | |
| { | |
| QString abs_path = this->BinaryDirectory->currentText(); | |
| if (!cmSystemTools::FileIsFullPath(abs_path.toStdString())) { | |
| if (!this->SourceDirectory->text().endsWith("/")) { | |
| abs_path = "/" + abs_path; | |
| } | |
| abs_path = this->SourceDirectory->text() + abs_path; | |
| } | |
| QString dir = QFileDialog::getExistingDirectory( | |
| this, tr("Enter Path to Build"), abs_path, | |
| QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); | |
| if (!dir.isEmpty() && dir != this->BinaryDirectory->currentText()) { | |
| this->setBinaryDirectory(dir); | |
| } | |
| } | |
| void CMakeSetupDialog::setBinaryDirectory(QString const& dir) | |
| { | |
| this->BinaryDirectory->setEditText(dir); | |
| } | |
| void CMakeSetupDialog::setStartupBinaryDirectory(bool startup) | |
| { | |
| this->StartupBinaryDirectory = startup; | |
| } | |
| void CMakeSetupDialog::onSourceDirectoryChanged(QString const& dir) | |
| { | |
| this->Output->clear(); | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), | |
| "setSourceDirectory", Qt::QueuedConnection, | |
| Q_ARG(QString, dir)); | |
| } | |
| void CMakeSetupDialog::onBinaryDirectoryChanged(QString const& dir) | |
| { | |
| QString title = QString(tr("CMake %1 - %2")); | |
| title = title.arg(cmVersion::GetCMakeVersion()); | |
| title = title.arg(dir); | |
| this->setWindowTitle(title); | |
| this->CacheModified = false; | |
| this->CacheValues->cacheModel()->clear(); | |
| qobject_cast<QCMakeCacheModelDelegate*>(this->CacheValues->itemDelegate()) | |
| ->clearChanges(); | |
| this->Output->clear(); | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), | |
| "setBinaryDirectory", Qt::QueuedConnection, | |
| Q_ARG(QString, dir)); | |
| } | |
| void CMakeSetupDialog::onBuildPresetChanged(QString const& name) | |
| { | |
| // Applying is async: gate the dependent actions now, until the completion | |
| // lands. | |
| quint64 const requestId = ++this->LatestPresetRequestId; | |
| this->PresetApplicationPending = true; | |
| // First Generate after an apply must reconfigure with the new inputs. | |
| this->ConfigureNeeded = true; | |
| this->updateCommandState(); | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "applyPreset", | |
| Qt::QueuedConnection, Q_ARG(QString, name), | |
| Q_ARG(bool, !this->StartupBinaryDirectory), | |
| Q_ARG(quint64, requestId)); | |
| this->StartupBinaryDirectory = false; | |
| } | |
| void CMakeSetupDialog::setSourceDirectory(QString const& dir) | |
| { | |
| this->SourceDirectory->setText(dir); | |
| } | |
| void CMakeSetupDialog::setDeferredPreset(QString const& preset) | |
| { | |
| this->DeferredPreset = preset; | |
| // A --preset isn't applied until presets load; gate now so nothing runs | |
| // with default inputs during that startup window. | |
| if (!preset.isNull()) { | |
| this->PresetApplicationPending = true; | |
| this->updateCommandState(); | |
| } | |
| } | |
| void CMakeSetupDialog::showProgress(QString const& /*msg*/, float percent) | |
| { | |
| percent = (percent * ProgressFactor) + ProgressOffset; | |
| this->ProgressBar->setValue(qRound(percent * 100)); | |
| #ifdef QT_WINEXTRAS | |
| QWinTaskbarProgress* progress = this->TaskbarButton->progress(); | |
| progress->setVisible(true); | |
| progress->setValue(qRound(percent * 100)); | |
| #endif | |
| } | |
| void CMakeSetupDialog::error(QString const& msg) | |
| { | |
| this->Output->setCurrentCharFormat(this->ErrorFormat); | |
| // QTextEdit will terminate the msg with a ParagraphSeparator, but it also | |
| // replaces | |
| // all newlines with ParagraphSeparators. By replacing the newlines by | |
| // ourself, one | |
| // error msg will be one paragraph. | |
| QString paragraph(msg); | |
| paragraph.replace(QLatin1Char('\n'), QChar::LineSeparator); | |
| this->Output->append(paragraph); | |
| } | |
| void CMakeSetupDialog::message(QString const& msg) | |
| { | |
| this->Output->setCurrentCharFormat(this->MessageFormat); | |
| this->Output->append(msg); | |
| } | |
| void CMakeSetupDialog::setEnabledState(bool enabled) | |
| { | |
| // disable parts of the GUI during configure/generate | |
| this->CacheValues->cacheModel()->setEditEnabled(enabled); | |
| this->SourceDirectory->setEnabled(enabled); | |
| this->BrowseSourceDirectoryButton->setEnabled(enabled); | |
| this->Preset->setEnabled(enabled && !this->Preset->presets().isEmpty()); | |
| this->BinaryDirectory->setEnabled(enabled); | |
| this->BrowseBinaryDirectoryButton->setEnabled(enabled); | |
| this->ReloadCacheAction->setEnabled(enabled); | |
| this->DeleteCacheAction->setEnabled(enabled); | |
| this->ExitAction->setEnabled(enabled); | |
| this->ConfigureAction->setEnabled(enabled); | |
| this->AddEntry->setEnabled(enabled); | |
| this->RemoveEntry->setEnabled(false); // let selection re-enable it | |
| this->Environment->setEnabled(enabled); | |
| } | |
| bool CMakeSetupDialog::setupFirstConfigure() | |
| { | |
| FirstConfigure dialog; | |
| // initialize dialog and restore saved settings | |
| // add generators | |
| dialog.setGenerators( | |
| this->CMakeThread->cmakeInstance()->availableGenerators()); | |
| // restore from settings | |
| dialog.loadFromSettings(); | |
| auto presetData = this->Preset->currentData(); | |
| if (presetData.isValid()) { | |
| auto preset = presetData.value<QCMakePreset>(); | |
| dialog.setCurrentGenerator(preset.generator); | |
| if (preset.setArchitecture) { | |
| dialog.setPlatform(preset.architecture); | |
| } | |
| if (preset.setToolset) { | |
| dialog.setToolset(preset.toolset); | |
| } | |
| dialog.setCompilerOption(CompilerOption::DefaultPreset); | |
| } | |
| if (dialog.exec() == QDialog::Accepted) { | |
| dialog.saveToSettings(); | |
| this->CMakeThread->cmakeInstance()->setGenerator(dialog.getGenerator()); | |
| this->CMakeThread->cmakeInstance()->setPlatform(dialog.getPlatform()); | |
| this->CMakeThread->cmakeInstance()->setToolset(dialog.getToolset()); | |
| QCMakeCacheModel* m = this->CacheValues->cacheModel(); | |
| if (dialog.compilerSetup()) { | |
| QString fortranCompiler = dialog.getFortranCompiler(); | |
| if (!fortranCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER", | |
| "Fortran compiler.", fortranCompiler, false); | |
| } | |
| QString cxxCompiler = dialog.getCXXCompiler(); | |
| if (!cxxCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER", | |
| "CXX compiler.", cxxCompiler, false); | |
| } | |
| QString cCompiler = dialog.getCCompiler(); | |
| if (!cCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER", | |
| "C compiler.", cCompiler, false); | |
| } | |
| } else if (dialog.crossCompilerSetup()) { | |
| QString fortranCompiler = dialog.getFortranCompiler(); | |
| if (!fortranCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER", | |
| "Fortran compiler.", fortranCompiler, false); | |
| } | |
| QString mode = dialog.getCrossIncludeMode(); | |
| m->insertProperty(QCMakeProperty::STRING, | |
| "CMAKE_FIND_ROOT_PATH_MODE_INCLUDE", | |
| tr("CMake Find Include Mode"), mode, false); | |
| mode = dialog.getCrossLibraryMode(); | |
| m->insertProperty(QCMakeProperty::STRING, | |
| "CMAKE_FIND_ROOT_PATH_MODE_LIBRARY", | |
| tr("CMake Find Library Mode"), mode, false); | |
| mode = dialog.getCrossProgramMode(); | |
| m->insertProperty(QCMakeProperty::STRING, | |
| "CMAKE_FIND_ROOT_PATH_MODE_PROGRAM", | |
| tr("CMake Find Program Mode"), mode, false); | |
| QString rootPath = dialog.getCrossRoot(); | |
| m->insertProperty(QCMakeProperty::PATH, "CMAKE_FIND_ROOT_PATH", | |
| tr("CMake Find Root Path"), rootPath, false); | |
| QString systemName = dialog.getSystemName(); | |
| m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_NAME", | |
| tr("CMake System Name"), systemName, false); | |
| QString systemVersion = dialog.getSystemVersion(); | |
| m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_VERSION", | |
| tr("CMake System Version"), systemVersion, false); | |
| QString systemProcessor = dialog.getSystemProcessor(); | |
| m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_PROCESSOR", | |
| tr("CMake System Processor"), systemProcessor, false); | |
| QString cxxCompiler = dialog.getCXXCompiler(); | |
| if (!cxxCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER", | |
| tr("CXX compiler."), cxxCompiler, false); | |
| } | |
| QString cCompiler = dialog.getCCompiler(); | |
| if (!cCompiler.isEmpty()) { | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER", | |
| tr("C compiler."), cCompiler, false); | |
| } | |
| } else if (dialog.crossCompilerToolChainFile()) { | |
| QString toolchainFile = dialog.getCrossCompilerToolChainFile(); | |
| m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_TOOLCHAIN_FILE", | |
| tr("Cross Compile ToolChain File"), toolchainFile, | |
| false); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| void CMakeSetupDialog::updateGeneratorLabel(QString const& gen) | |
| { | |
| QString str = tr("Current Generator: "); | |
| if (gen.isEmpty()) { | |
| str += tr("None"); | |
| } else { | |
| str += gen; | |
| } | |
| this->Generator->setText(str); | |
| } | |
| void CMakeSetupDialog::doReloadCache() | |
| { | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "reloadCache", | |
| Qt::QueuedConnection); | |
| } | |
| void CMakeSetupDialog::doDeleteCache() | |
| { | |
| QString title = tr("Delete Cache"); | |
| QString msg = tr("Are you sure you want to delete the cache?"); | |
| QMessageBox::StandardButton btn; | |
| btn = QMessageBox::information(this, title, msg, | |
| QMessageBox::Yes | QMessageBox::No); | |
| if (btn == QMessageBox::No) { | |
| return; | |
| } | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "deleteCache", | |
| Qt::QueuedConnection); | |
| } | |
| void CMakeSetupDialog::doAbout() | |
| { | |
| QString msg = tr( | |
| "CMake %1 (cmake.org).\n" | |
| "CMake suite maintained and supported by Kitware (kitware.com/cmake).\n" | |
| "Distributed under terms of the BSD 3-Clause License.\n" | |
| "\n" | |
| "CMake GUI built using Qt %2 (qt-project.org).\n" | |
| #ifdef USE_LGPL | |
| "\n" | |
| "The Qt Toolkit is Copyright (C) The Qt Company Ltd.\n" | |
| "Qt is licensed under terms of the GNU LGPLv" USE_LGPL ", available at:\n" | |
| " \"%3\"" | |
| #endif | |
| ); | |
| msg = msg.arg(cmVersion::GetCMakeVersion()); | |
| msg = msg.arg(qVersion()); | |
| #ifdef USE_LGPL | |
| std::string lgpl = | |
| cmSystemTools::GetCMakeRoot() + "/Licenses/LGPLv" USE_LGPL ".txt"; | |
| msg = msg.arg(lgpl.c_str()); | |
| #endif | |
| QDialog dialog; | |
| dialog.setWindowTitle(tr("About")); | |
| QVBoxLayout* l = new QVBoxLayout(&dialog); | |
| QLabel* lab = new QLabel(&dialog); | |
| l->addWidget(lab); | |
| lab->setText(msg); | |
| lab->setWordWrap(true); | |
| QDialogButtonBox* btns = | |
| new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog); | |
| QObject::connect(btns, &QDialogButtonBox::accepted, &dialog, | |
| &QDialog::accept); | |
| l->addWidget(btns); | |
| dialog.exec(); | |
| } | |
| void CMakeSetupDialog::setExitAfterGenerate(bool b) | |
| { | |
| this->ExitAfterGenerate = b; | |
| } | |
| void CMakeSetupDialog::addBinaryPath(QString const& path) | |
| { | |
| QString cleanpath = QDir::cleanPath(path); | |
| // update UI | |
| this->BinaryDirectory->blockSignals(true); | |
| int idx = this->BinaryDirectory->findText(cleanpath); | |
| if (idx != -1) { | |
| this->BinaryDirectory->removeItem(idx); | |
| } | |
| this->BinaryDirectory->insertItem(0, cleanpath); | |
| this->BinaryDirectory->setCurrentIndex(0); | |
| this->BinaryDirectory->blockSignals(false); | |
| // save to registry | |
| QStringList buildPaths = this->loadBuildPaths(); | |
| buildPaths.removeAll(cleanpath); | |
| buildPaths.prepend(cleanpath); | |
| this->saveBuildPaths(buildPaths); | |
| } | |
| void CMakeSetupDialog::dragEnterEvent(QDragEnterEvent* e) | |
| { | |
| if (!(this->CurrentState == ReadyConfigure || | |
| this->CurrentState == ReadyGenerate)) { | |
| e->ignore(); | |
| return; | |
| } | |
| QMimeData const* dat = e->mimeData(); | |
| QList<QUrl> urls = dat->urls(); | |
| QString file = urls.count() ? urls[0].toLocalFile() : QString(); | |
| if (!file.isEmpty() && | |
| (file.endsWith("CMakeCache.txt", Qt::CaseInsensitive) || | |
| file.endsWith("CMakeLists.txt", Qt::CaseInsensitive))) { | |
| e->accept(); | |
| } else { | |
| e->ignore(); | |
| } | |
| } | |
| void CMakeSetupDialog::dropEvent(QDropEvent* e) | |
| { | |
| if (!(this->CurrentState == ReadyConfigure || | |
| this->CurrentState == ReadyGenerate)) { | |
| return; | |
| } | |
| QMimeData const* dat = e->mimeData(); | |
| QList<QUrl> urls = dat->urls(); | |
| QString file = urls.count() ? urls[0].toLocalFile() : QString(); | |
| if (file.endsWith("CMakeCache.txt", Qt::CaseInsensitive)) { | |
| QFileInfo info(file); | |
| if (this->CMakeThread->cmakeInstance()->binaryDirectory() != | |
| info.absolutePath()) { | |
| this->setBinaryDirectory(info.absolutePath()); | |
| } | |
| } else if (file.endsWith("CMakeLists.txt", Qt::CaseInsensitive)) { | |
| QFileInfo info(file); | |
| if (this->CMakeThread->cmakeInstance()->binaryDirectory() != | |
| info.absolutePath()) { | |
| this->setSourceDirectory(info.absolutePath()); | |
| this->setBinaryDirectory(info.absolutePath()); | |
| } | |
| } | |
| } | |
| QStringList CMakeSetupDialog::loadBuildPaths() | |
| { | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| QStringList buildPaths; | |
| for (int i = 0; i < 10; i++) { | |
| QString p = settings.value(QString("WhereBuild%1").arg(i)).toString(); | |
| if (!p.isEmpty()) { | |
| buildPaths.append(p); | |
| } | |
| } | |
| return buildPaths; | |
| } | |
| void CMakeSetupDialog::saveBuildPaths(QStringList const& paths) | |
| { | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| cm_qsizetype num = paths.count(); | |
| if (num > 10) { | |
| num = 10; | |
| } | |
| for (cm_qsizetype i = 0; i < num; i++) { | |
| settings.setValue(QString("WhereBuild%1").arg(i), paths[i]); | |
| } | |
| } | |
| void CMakeSetupDialog::setCacheModified() | |
| { | |
| this->CacheModified = true; | |
| this->ConfigureNeeded = true; | |
| this->enterState(ReadyConfigure); | |
| } | |
| void CMakeSetupDialog::removeSelectedCacheEntries() | |
| { | |
| QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows(); | |
| QList<QPersistentModelIndex> pidxs; | |
| foreach (QModelIndex const& i, idxs) { | |
| pidxs.append(i); | |
| } | |
| foreach (QPersistentModelIndex const& pi, pidxs) { | |
| this->CacheValues->model()->removeRow(pi.row(), pi.parent()); | |
| } | |
| } | |
| void CMakeSetupDialog::selectionChanged() | |
| { | |
| QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows(); | |
| if (idxs.count() && | |
| (this->CurrentState == ReadyConfigure || | |
| this->CurrentState == ReadyGenerate)) { | |
| this->RemoveEntry->setEnabled(true); | |
| } else { | |
| this->RemoveEntry->setEnabled(false); | |
| } | |
| } | |
| void CMakeSetupDialog::enterState(CMakeSetupDialog::State s) | |
| { | |
| if (s == this->CurrentState) { | |
| return; | |
| } | |
| this->CurrentState = s; | |
| if (s == Interrupting) { | |
| this->ConfigureButton->setEnabled(false); | |
| this->GenerateButton->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| } else if (s == Configuring) { | |
| this->setEnabledState(false); | |
| this->GenerateButton->setEnabled(false); | |
| this->GenerateAction->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| this->ConfigureButton->setText(tr("&Stop")); | |
| } else if (s == Generating) { | |
| this->CacheModified = false; | |
| this->setEnabledState(false); | |
| this->ConfigureButton->setEnabled(false); | |
| this->GenerateAction->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| this->GenerateButton->setText(tr("&Stop")); | |
| } else if (s == ReadyConfigure || s == ReadyGenerate) { | |
| this->setEnabledState(true); | |
| this->ConfigureButton->setText(tr("&Configure")); | |
| this->GenerateButton->setText(tr("&Generate")); | |
| this->updateCommandState(); | |
| } | |
| } | |
| void CMakeSetupDialog::updateCommandState() | |
| { | |
| bool const pending = this->PresetApplicationPending; | |
| switch (this->CurrentState) { | |
| case Interrupting: | |
| this->ConfigureButton->setEnabled(false); | |
| this->GenerateButton->setEnabled(false); | |
| this->ConfigureAction->setEnabled(false); | |
| this->GenerateAction->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| break; | |
| case Configuring: | |
| this->ConfigureButton->setEnabled(true); // acts as Stop | |
| this->GenerateButton->setEnabled(false); | |
| this->ConfigureAction->setEnabled(false); | |
| this->GenerateAction->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| break; | |
| case Generating: | |
| this->GenerateButton->setEnabled(true); // acts as Stop | |
| this->ConfigureButton->setEnabled(false); | |
| this->ConfigureAction->setEnabled(false); | |
| this->GenerateAction->setEnabled(false); | |
| this->OpenProjectButton->setEnabled(false); | |
| break; | |
| case ReadyConfigure: | |
| case ReadyGenerate: { | |
| bool const enabled = !pending; | |
| this->ConfigureButton->setEnabled(enabled); | |
| this->GenerateButton->setEnabled(enabled); | |
| this->ConfigureAction->setEnabled(enabled); | |
| this->GenerateAction->setEnabled(enabled); | |
| // Gate everything that reads or mutates the still-stale model, but keep | |
| // the combo live so the user can pick a different preset. | |
| this->CacheValues->cacheModel()->setEditEnabled(enabled); | |
| this->SourceDirectory->setEnabled(enabled); | |
| this->BrowseSourceDirectoryButton->setEnabled(enabled); | |
| this->BinaryDirectory->setEnabled(enabled); | |
| this->BrowseBinaryDirectoryButton->setEnabled(enabled); | |
| this->ReloadCacheAction->setEnabled(enabled); | |
| this->DeleteCacheAction->setEnabled(enabled); | |
| this->ReloadPresetsButton->setEnabled(enabled); | |
| this->AddEntry->setEnabled(enabled); | |
| this->Environment->setEnabled(enabled); | |
| this->Preset->setEnabled(!this->Preset->presets().isEmpty()); | |
| if (enabled) { | |
| this->selectionChanged(); // let selection re-enable Remove | |
| } else { | |
| this->RemoveEntry->setEnabled(false); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| void CMakeSetupDialog::editEnvironment() | |
| { | |
| EnvironmentDialog dialog(this->CMakeThread->cmakeInstance()->environment(), | |
| this); | |
| if (dialog.exec() == QDialog::Accepted) { | |
| QMetaObject::invokeMethod( | |
| this->CMakeThread->cmakeInstance(), "setEnvironment", | |
| Q_ARG(QProcessEnvironment, dialog.environment())); | |
| } | |
| } | |
| void CMakeSetupDialog::addCacheEntry() | |
| { | |
| QDialog dialog(this); | |
| dialog.resize(400, 200); | |
| dialog.setWindowTitle(tr("Add Cache Entry")); | |
| QVBoxLayout* l = new QVBoxLayout(&dialog); | |
| AddCacheEntry* w = | |
| new AddCacheEntry(&dialog, this->AddVariableNames, this->AddVariableTypes); | |
| QDialogButtonBox* btns = new QDialogButtonBox( | |
| QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); | |
| QObject::connect(btns, &QDialogButtonBox::accepted, &dialog, | |
| &QDialog::accept); | |
| QObject::connect(btns, &QDialogButtonBox::rejected, &dialog, | |
| &QDialog::reject); | |
| l->addWidget(w); | |
| l->addStretch(); | |
| l->addWidget(btns); | |
| if (QDialog::Accepted == dialog.exec()) { | |
| QCMakeCacheModel* m = this->CacheValues->cacheModel(); | |
| m->insertProperty(w->type(), w->name(), w->description(), w->value(), | |
| false); | |
| // only add variable names to the completion which are new | |
| if (!this->AddVariableNames.contains(w->name())) { | |
| this->AddVariableNames << w->name(); | |
| this->AddVariableTypes << w->typeString(); | |
| // limit to at most 100 completion items | |
| if (this->AddVariableNames.size() > 100) { | |
| this->AddVariableNames.removeFirst(); | |
| this->AddVariableTypes.removeFirst(); | |
| } | |
| // make sure CMAKE_INSTALL_PREFIX is always there | |
| if (!this->AddVariableNames.contains("CMAKE_INSTALL_PREFIX")) { | |
| this->AddVariableNames << "CMAKE_INSTALL_PREFIX"; | |
| this->AddVariableTypes << "PATH"; | |
| } | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| settings.setValue("AddVariableNames", this->AddVariableNames); | |
| settings.setValue("AddVariableTypes", this->AddVariableTypes); | |
| } | |
| } | |
| } | |
| void CMakeSetupDialog::startSearch() | |
| { | |
| this->Search->setFocus(Qt::OtherFocusReason); | |
| this->Search->selectAll(); | |
| } | |
| void CMakeSetupDialog::setDebugOutput(bool flag) | |
| { | |
| QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), | |
| "setDebugOutput", Qt::QueuedConnection, | |
| Q_ARG(bool, flag)); | |
| } | |
| void CMakeSetupDialog::setGroupedView(bool v) | |
| { | |
| this->CacheValues->cacheModel()->setViewType(v ? QCMakeCacheModel::GroupView | |
| : QCMakeCacheModel::FlatView); | |
| this->CacheValues->setRootIsDecorated(v); | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| settings.setValue("GroupView", v); | |
| } | |
| void CMakeSetupDialog::setAdvancedView(bool v) | |
| { | |
| this->CacheValues->setShowAdvanced(v); | |
| QSettings settings; | |
| settings.beginGroup("Settings/StartPath"); | |
| settings.setValue("AdvancedView", v); | |
| } | |
| void CMakeSetupDialog::showUserChanges() | |
| { | |
| QSet<QCMakeProperty> changes = | |
| qobject_cast<QCMakeCacheModelDelegate*>(this->CacheValues->itemDelegate()) | |
| ->changes(); | |
| QDialog dialog(this); | |
| dialog.setWindowTitle(tr("My Changes")); | |
| dialog.resize(600, 400); | |
| QVBoxLayout* l = new QVBoxLayout(&dialog); | |
| QTextEdit* textedit = new QTextEdit(&dialog); | |
| textedit->setReadOnly(true); | |
| l->addWidget(textedit); | |
| QDialogButtonBox* btns = | |
| new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &dialog); | |
| QObject::connect(btns, &QDialogButtonBox::rejected, &dialog, | |
| &QDialog::accept); | |
| l->addWidget(btns); | |
| QString command; | |
| QString cache; | |
| foreach (QCMakeProperty const& prop, changes) { | |
| QString type; | |
| switch (prop.Type) { | |
| case QCMakeProperty::BOOL: | |
| type = "BOOL"; | |
| break; | |
| case QCMakeProperty::PATH: | |
| type = "PATH"; | |
| break; | |
| case QCMakeProperty::FILEPATH: | |
| type = "FILEPATH"; | |
| break; | |
| case QCMakeProperty::STRING: | |
| type = "STRING"; | |
| break; | |
| } | |
| QString value; | |
| if (prop.Type == QCMakeProperty::BOOL) { | |
| value = prop.Value.toBool() ? "1" : "0"; | |
| } else { | |
| value = prop.Value.toString(); | |
| } | |
| QString const line = QString("%1:%2=").arg(prop.Key, type); | |
| command += QString("-D%1\"%2\" ").arg(line, value); | |
| cache += QString("%1%2\n").arg(line, value); | |
| } | |
| textedit->append(tr("Commandline options:")); | |
| textedit->append(command); | |
| textedit->append("\n"); | |
| textedit->append(tr("Cache file:")); | |
| textedit->append(cache); | |
| dialog.exec(); | |
| } | |
| void CMakeSetupDialog::setSearchFilter(QString const& str) | |
| { | |
| this->CacheValues->selectionModel()->clear(); | |
| bool const valid = this->CacheValues->setSearchFilter(str); | |
| QtCMake::setSearchFilterColor(this->Search, valid); | |
| } | |
| void CMakeSetupDialog::doOutputContextMenu(QPoint pt) | |
| { | |
| std::unique_ptr<QMenu> menu(this->Output->createStandardContextMenu()); | |
| menu->addSeparator(); | |
| auto* a = menu->addAction(tr("Find...")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindDialog); | |
| a->setShortcut(QKeySequence::Find); | |
| a = menu->addAction(tr("Find Next")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindNext); | |
| a->setShortcut(QKeySequence::FindNext); | |
| a = menu->addAction(tr("Find Previous")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputFindPrev); | |
| a->setShortcut(QKeySequence::FindPrevious); | |
| menu->addSeparator(); | |
| a = menu->addAction(tr("Goto Next Error")); | |
| QObject::connect(a, &QAction::triggered, this, | |
| &CMakeSetupDialog::doOutputErrorNext); | |
| a->setShortcut(QKeySequence(Qt::Key_F8)); | |
| menu->exec(this->Output->mapToGlobal(pt)); | |
| } | |
| void CMakeSetupDialog::doOutputFindDialog() | |
| { | |
| QStringList strings(this->FindHistory); | |
| QString selection = this->Output->textCursor().selectedText(); | |
| if (!selection.isEmpty() && !selection.contains(QChar::ParagraphSeparator) && | |
| !selection.contains(QChar::LineSeparator)) { | |
| strings.push_front(selection); | |
| } | |
| bool ok; | |
| QString search = QInputDialog::getItem(this, tr("Find in Output"), | |
| tr("Find:"), strings, 0, true, &ok); | |
| if (ok && !search.isEmpty()) { | |
| if (!this->FindHistory.contains(search)) { | |
| this->FindHistory.push_front(search); | |
| } | |
| doOutputFindNext(); | |
| } | |
| } | |
| void CMakeSetupDialog::doRegexExplorerDialog() | |
| { | |
| RegexExplorer dialog(this); | |
| dialog.exec(); | |
| } | |
| void CMakeSetupDialog::doOutputFindPrev() | |
| { | |
| doOutputFindNext(false); | |
| } | |
| void CMakeSetupDialog::doOutputFindNext(bool directionForward) | |
| { | |
| if (this->FindHistory.isEmpty()) { | |
| doOutputFindDialog(); // will re-call this function again | |
| return; | |
| } | |
| QString search = this->FindHistory.front(); | |
| QTextCursor textCursor = this->Output->textCursor(); | |
| QTextDocument* document = this->Output->document(); | |
| QTextDocument::FindFlags flags; | |
| if (!directionForward) { | |
| flags |= QTextDocument::FindBackward; | |
| } | |
| textCursor = document->find(search, textCursor, flags); | |
| if (textCursor.isNull()) { | |
| // first search found nothing, wrap around and search again | |
| textCursor = this->Output->textCursor(); | |
| textCursor.movePosition(directionForward ? QTextCursor::Start | |
| : QTextCursor::End); | |
| textCursor = document->find(search, textCursor, flags); | |
| } | |
| if (textCursor.hasSelection()) { | |
| this->Output->setTextCursor(textCursor); | |
| } | |
| } | |
| void CMakeSetupDialog::doOutputErrorNext() | |
| { | |
| QTextCursor textCursor = this->Output->textCursor(); | |
| bool atEnd = false; | |
| // move cursor out of current error-block | |
| if (textCursor.blockCharFormat() == this->ErrorFormat) { | |
| atEnd = !textCursor.movePosition(QTextCursor::NextBlock); | |
| } | |
| // move cursor to next error-block | |
| while (textCursor.blockCharFormat() != this->ErrorFormat && !atEnd) { | |
| atEnd = !textCursor.movePosition(QTextCursor::NextBlock); | |
| } | |
| if (atEnd) { | |
| // first search found nothing, wrap around and search again | |
| atEnd = !textCursor.movePosition(QTextCursor::Start); | |
| // move cursor to next error-block | |
| while (textCursor.blockCharFormat() != this->ErrorFormat && !atEnd) { | |
| atEnd = !textCursor.movePosition(QTextCursor::NextBlock); | |
| } | |
| } | |
| if (!atEnd) { | |
| textCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); | |
| QTextCharFormat selectionFormat; | |
| selectionFormat.setBackground(Qt::yellow); | |
| QTextEdit::ExtraSelection extraSelection = { textCursor, selectionFormat }; | |
| this->Output->setExtraSelections(QList<QTextEdit::ExtraSelection>() | |
| << extraSelection); | |
| // make the whole error-block visible | |
| this->Output->setTextCursor(textCursor); | |
| // remove the selection to see the extraSelection | |
| textCursor.setPosition(textCursor.anchor()); | |
| this->Output->setTextCursor(textCursor); | |
| } | |
| } | |
| void CMakeSetupDialog::doWarningMessagesDialog() | |
| { | |
| WarningMessagesDialog dialog(this, this->CMakeThread->cmakeInstance()); | |
| dialog.exec(); | |
| } |