/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "CMakeSetupDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QT_WINEXTRAS # include # include #endif #include "QCMake.h" #include "QCMakeCacheView.h" #include "cmSystemTools.h" #include "cmVersion.h" #include "AddCacheEntry.h" #include "EnvironmentDialog.h" #include "FirstConfigure.h" #include "RegexExplorer.h" #include "WarningMessagesDialog.h" void OpenReferenceManual() { 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::fromLocal8Bit(cmSystemTools::GetHTMLDoc().data())) .filePath("index.html")); } QDesktopServices::openUrl(url); } namespace { const QString 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(); // 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); this->WarnUninitializedAction = OptionsMenu->addAction(tr("&Warn Uninitialized (--warn-uninitialized)")); this->WarnUninitializedAction->setCheckable(true); 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); 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"); 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->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::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); QObject::connect(this->WarnUninitializedAction, &QAction::triggered, this->CMakeThread->cmakeInstance(), &QCMake::setWarnUninitializedMode); QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::warnUninitializedModeChanged, this->WarnUninitializedAction, &QAction::setChecked); if (!this->SourceDirectory->text().isEmpty() && !this->DeferredPreset.isNull()) { this->onSourceDirectoryChanged(this->SourceDirectory->text()); } else if (!this->SourceDirectory->text().isEmpty() || !this->BinaryDirectory->lineEdit()->text().isEmpty()) { 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 path this->addBinaryPath(dir.absolutePath()); return true; } void CMakeSetupDialog::exitLoop(int err) { this->LocalLoop.exit(err); } void CMakeSetupDialog::doConfigure() { if (this->CurrentState == Configuring) { // stop configure doInterrupt(); 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; } // 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(const QString& dir) { if (this->SourceDirectory->text() != dir) { this->SourceDirectory->blockSignals(true); this->SourceDirectory->setText(dir); this->SourceDirectory->blockSignals(false); } } void CMakeSetupDialog::updateBinaryDirectory(const QString& dir) { if (this->BinaryDirectory->currentText() != dir) { this->BinaryDirectory->blockSignals(true); this->BinaryDirectory->setEditText(dir); this->BinaryDirectory->blockSignals(false); } } void CMakeSetupDialog::updatePresets(const QVector& 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()) { this->Preset->setPresetName(this->DeferredPreset); this->DeferredPreset = QString{}; } } void CMakeSetupDialog::updatePreset(const QString& name) { if (this->Preset->presetName() != name) { this->Preset->blockSignals(true); this->Preset->setPresetName(name); this->Preset->blockSignals(false); } } void CMakeSetupDialog::showPresetLoadError( const QString& dir, cmCMakePresetsGraph::ReadFileResult result) { QMessageBox::warning( this, "Error Reading CMake Presets", QString::fromLocal8Bit("Could not read presets from %1: %2") .arg(dir, cmCMakePresetsGraph::ResultToString(result))); } void CMakeSetupDialog::doBinaryBrowse() { QString dir = QFileDialog::getExistingDirectory( this, tr("Enter Path to Build"), this->BinaryDirectory->currentText(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!dir.isEmpty() && dir != this->BinaryDirectory->currentText()) { this->setBinaryDirectory(dir); } } void CMakeSetupDialog::setBinaryDirectory(const QString& dir) { this->BinaryDirectory->setEditText(dir); } void CMakeSetupDialog::setStartupBinaryDirectory(bool startup) { this->StartupBinaryDirectory = startup; } void CMakeSetupDialog::onSourceDirectoryChanged(const QString& dir) { this->Output->clear(); QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setSourceDirectory", Qt::QueuedConnection, Q_ARG(QString, dir)); } void CMakeSetupDialog::onBinaryDirectoryChanged(const QString& 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(this->CacheValues->itemDelegate()) ->clearChanges(); this->Output->clear(); QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setBinaryDirectory", Qt::QueuedConnection, Q_ARG(QString, dir)); } void CMakeSetupDialog::onBuildPresetChanged(const QString& name) { QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setPreset", Qt::QueuedConnection, Q_ARG(QString, name), Q_ARG(bool, !this->StartupBinaryDirectory)); this->StartupBinaryDirectory = false; } void CMakeSetupDialog::setSourceDirectory(const QString& dir) { this->SourceDirectory->setText(dir); } void CMakeSetupDialog::setDeferredPreset(const QString& preset) { this->DeferredPreset = preset; } void CMakeSetupDialog::showProgress(const QString& /*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(const QString& 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(const QString& 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(); dialog.setCurrentGenerator(preset.generator); if (preset.setArchitecture) { dialog.setPlatform(preset.architecture); } if (preset.setToolset) { dialog.setToolset(preset.toolset); } dialog.setCompilerOption(CompilerOption::DefaultNative); } 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(const QString& 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 maintained by csimsoft,\n" "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(const QString& 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; } const QMimeData* dat = e->mimeData(); QList 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; } const QMimeData* dat = e->mimeData(); QList 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(const QStringList& paths) { QSettings settings; settings.beginGroup("Settings/StartPath"); int num = paths.count(); if (num > 10) { num = 10; } for (int 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 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->GenerateButton->setEnabled(true); this->GenerateAction->setEnabled(true); this->ConfigureButton->setEnabled(true); this->ConfigureButton->setText(tr("&Configure")); this->GenerateButton->setText(tr("&Generate")); } } 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 changes = qobject_cast(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(const QString& str) { this->CacheValues->selectionModel()->clear(); this->CacheValues->setSearchFilter(str); } void CMakeSetupDialog::doOutputContextMenu(QPoint pt) { std::unique_ptr 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() << 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(); }