You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cmake/Source/QtDialog/CMakeSetupDialog.cxx

1517 lines
50 KiB

/* 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 <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 "QCMake.h"
#include "QCMakeCacheView.h"
#include "QCMakeSizeType.h"
#include "cmSystemTools.h"
#include "cmVersion.h"
#include "AddCacheEntry.h"
#include "EnvironmentDialog.h"
#include "FirstConfigure.h"
#include "RegexExplorer.h"
#include "WarningMessagesDialog.h"
void OpenReferenceManual(const QString& 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 {
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<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);
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("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->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());
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;
}
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<QCMakePreset>& 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,
const QString& 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(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<QCMakeCacheModelDelegate*>(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<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(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<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;
}
const QMimeData* 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(const QStringList& 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->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<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(const QString& str)
{
this->CacheValues->selectionModel()->clear();
const bool 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();
}