1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "CMakeSetupDialog.h"
4 
5 #include <cm/memory>
6 
7 #include <QCloseEvent>
8 #include <QCoreApplication>
9 #include <QDesktopServices>
10 #include <QDialogButtonBox>
11 #include <QDragEnterEvent>
12 #include <QFileDialog>
13 #include <QInputDialog>
14 #include <QKeySequence>
15 #include <QMenu>
16 #include <QMenuBar>
17 #include <QMessageBox>
18 #include <QMimeData>
19 #include <QProcessEnvironment>
20 #include <QProgressBar>
21 #include <QSettings>
22 #include <QShortcut>
23 #include <QStatusBar>
24 #include <QString>
25 #include <QUrl>
26 #include <QVector>
27 
28 #ifdef QT_WINEXTRAS
29 #  include <QWinTaskbarButton>
30 #  include <QWinTaskbarProgress>
31 #endif
32 
33 #include "QCMake.h"
34 #include "QCMakeCacheView.h"
35 
36 #include "cmSystemTools.h"
37 #include "cmVersion.h"
38 
39 #include "AddCacheEntry.h"
40 #include "EnvironmentDialog.h"
41 #include "FirstConfigure.h"
42 #include "RegexExplorer.h"
43 #include "WarningMessagesDialog.h"
44 
OpenReferenceManual()45 void OpenReferenceManual()
46 {
47   QString urlFormat("https://cmake.org/cmake/help/v%1.%2/");
48   QUrl url(urlFormat.arg(QString::number(cmVersion::GetMajorVersion()),
49                          QString::number(cmVersion::GetMinorVersion())));
50 
51   if (!cmSystemTools::GetHTMLDoc().empty()) {
52     url = QUrl::fromLocalFile(
53       QDir(QString::fromLocal8Bit(cmSystemTools::GetHTMLDoc().data()))
54         .filePath("index.html"));
55   }
56 
57   QDesktopServices::openUrl(url);
58 }
59 
60 namespace {
61 const QString PRESETS_DISABLED_TOOLTIP =
62   "This option is disabled because there are no available presets in "
63   "CMakePresets.json or CMakeUserPresets.json.";
64 }
65 
QCMakeThread(QObject * p)66 QCMakeThread::QCMakeThread(QObject* p)
67   : QThread(p)
68 {
69 }
70 
cmakeInstance() const71 QCMake* QCMakeThread::cmakeInstance() const
72 {
73   return this->CMakeInstance.get();
74 }
75 
run()76 void QCMakeThread::run()
77 {
78   this->CMakeInstance = cm::make_unique<QCMake>();
79   // emit that this cmake thread is ready for use
80   emit this->cmakeInitialized();
81   this->exec();
82   this->CMakeInstance.reset();
83 }
84 
CMakeSetupDialog()85 CMakeSetupDialog::CMakeSetupDialog()
86   : ExitAfterGenerate(true)
87   , CacheModified(false)
88   , ConfigureNeeded(true)
89   , CurrentState(Interrupting)
90 {
91   QString title = QString(tr("CMake %1"));
92   title = title.arg(cmVersion::GetCMakeVersion());
93   this->setWindowTitle(title);
94 
95   // create the GUI
96   QSettings settings;
97   settings.beginGroup("Settings/StartPath");
98   restoreGeometry(settings.value("geometry").toByteArray());
99   restoreState(settings.value("windowState").toByteArray());
100 
101   this->AddVariableNames =
102     settings.value("AddVariableNames", QStringList("CMAKE_INSTALL_PREFIX"))
103       .toStringList();
104   this->AddVariableTypes =
105     settings.value("AddVariableTypes", QStringList("PATH")).toStringList();
106 
107   QWidget* cont = new QWidget(this);
108   this->setupUi(cont);
109   this->Splitter->setStretchFactor(0, 3);
110   this->Splitter->setStretchFactor(1, 1);
111   this->setCentralWidget(cont);
112   this->ProgressBar->reset();
113   this->RemoveEntry->setEnabled(false);
114   this->AddEntry->setEnabled(false);
115   this->Preset->setStatusTip(PRESETS_DISABLED_TOOLTIP);
116 
117   QByteArray p = settings.value("SplitterSizes").toByteArray();
118   this->Splitter->restoreState(p);
119 
120   bool groupView = settings.value("GroupView", false).toBool();
121   this->setGroupedView(groupView);
122   this->groupedCheck->setCheckState(groupView ? Qt::Checked : Qt::Unchecked);
123 
124   bool advancedView = settings.value("AdvancedView", false).toBool();
125   this->setAdvancedView(advancedView);
126   this->advancedCheck->setCheckState(advancedView ? Qt::Checked
127                                                   : Qt::Unchecked);
128 
129   QMenu* FileMenu = this->menuBar()->addMenu(tr("&File"));
130   this->ReloadCacheAction = FileMenu->addAction(tr("&Reload Cache"));
131   QObject::connect(this->ReloadCacheAction, &QAction::triggered, this,
132                    &CMakeSetupDialog::doReloadCache);
133   this->DeleteCacheAction = FileMenu->addAction(tr("&Delete Cache"));
134   QObject::connect(this->DeleteCacheAction, &QAction::triggered, this,
135                    &CMakeSetupDialog::doDeleteCache);
136   this->ExitAction = FileMenu->addAction(tr("E&xit"));
137   QObject::connect(this->ExitAction, &QAction::triggered, this,
138                    &CMakeSetupDialog::close);
139   this->ExitAction->setShortcut(QKeySequence::Quit);
140 
141   QMenu* ToolsMenu = this->menuBar()->addMenu(tr("&Tools"));
142   this->ConfigureAction = ToolsMenu->addAction(tr("&Configure"));
143   QObject::connect(this->ConfigureAction, &QAction::triggered, this,
144                    &CMakeSetupDialog::doConfigure);
145   // prevent merging with Preferences menu item on macOS
146   this->ConfigureAction->setMenuRole(QAction::NoRole);
147   this->GenerateAction = ToolsMenu->addAction(tr("&Generate"));
148   QObject::connect(this->GenerateAction, &QAction::triggered, this,
149                    &CMakeSetupDialog::doGenerate);
150   auto* a = ToolsMenu->addAction(tr("&Show My Changes"));
151   QObject::connect(a, &QAction::triggered, this,
152                    &CMakeSetupDialog::showUserChanges);
153 #if defined(Q_WS_MAC) || defined(Q_OS_MAC)
154   this->InstallForCommandLineAction =
155     ToolsMenu->addAction(tr("&How to Install For Command Line Use"));
156   QObject::connect(this->InstallForCommandLineAction, &QAction::triggered,
157                    this, &CMakeSetupDialog::doInstallForCommandLine);
158 #endif
159   ToolsMenu->addSeparator();
160   a = ToolsMenu->addAction(tr("Regular Expression Explorer..."));
161   QObject::connect(a, &QAction::triggered, this,
162                    &CMakeSetupDialog::doRegexExplorerDialog);
163   ToolsMenu->addSeparator();
164   a = ToolsMenu->addAction(tr("&Find in Output..."));
165   QObject::connect(a, &QAction::triggered, this,
166                    &CMakeSetupDialog::doOutputFindDialog);
167   a->setShortcut(QKeySequence::Find);
168   a = ToolsMenu->addAction(tr("Find Next"));
169   QObject::connect(a, &QAction::triggered, this,
170                    &CMakeSetupDialog::doOutputFindNext);
171   a->setShortcut(QKeySequence::FindNext);
172   a = ToolsMenu->addAction(tr("Find Previous"));
173   QObject::connect(a, &QAction::triggered, this,
174                    &CMakeSetupDialog::doOutputFindPrev);
175   a->setShortcut(QKeySequence::FindPrevious);
176   a = ToolsMenu->addAction(tr("Goto Next Error")); // in Visual Studio
177   QObject::connect(a, &QAction::triggered, this,
178                    &CMakeSetupDialog::doOutputErrorNext);
179   a->setShortcut(QKeySequence(Qt::Key_F8));
180   auto* s = new QShortcut(this);
181 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
182   s->setKey(QKeySequence(Qt::CTRL + Qt::Key_Period));
183 #else
184   s->setKey(QKeySequence(Qt::CTRL | Qt::Key_Period));
185 #endif
186   QObject::connect(s, &QShortcut::activated, this,
187                    &CMakeSetupDialog::doOutputErrorNext); // in Eclipse
188 
189   QMenu* OptionsMenu = this->menuBar()->addMenu(tr("&Options"));
190   a = OptionsMenu->addAction(tr("Warning Messages..."));
191   QObject::connect(a, &QAction::triggered, this,
192                    &CMakeSetupDialog::doWarningMessagesDialog);
193   this->WarnUninitializedAction =
194     OptionsMenu->addAction(tr("&Warn Uninitialized (--warn-uninitialized)"));
195   this->WarnUninitializedAction->setCheckable(true);
196 
197   QAction* debugAction = OptionsMenu->addAction(tr("&Debug Output"));
198   debugAction->setCheckable(true);
199   QObject::connect(debugAction, &QAction::toggled, this,
200                    &CMakeSetupDialog::setDebugOutput);
201 
202   OptionsMenu->addSeparator();
203   a = OptionsMenu->addAction(tr("&Expand Grouped Entries"));
204   QObject::connect(a, &QAction::triggered, this->CacheValues,
205                    &QCMakeCacheView::expandAll);
206   a = OptionsMenu->addAction(tr("&Collapse Grouped Entries"));
207   QObject::connect(a, &QAction::triggered, this->CacheValues,
208                    &QCMakeCacheView::collapseAll);
209 
210   QMenu* HelpMenu = this->menuBar()->addMenu(tr("&Help"));
211   a = HelpMenu->addAction(tr("Help"));
212   QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doHelp);
213   a->setShortcut(QKeySequence::HelpContents);
214   a = HelpMenu->addAction(tr("CMake Reference Manual"));
215   QObject::connect(a, &QAction::triggered, this, OpenReferenceManual);
216   a = HelpMenu->addAction(tr("About"));
217   QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doAbout);
218 
219   this->setAcceptDrops(true);
220 
221   // get the saved binary directories
222   QStringList buildPaths = this->loadBuildPaths();
223   this->BinaryDirectory->addItems(buildPaths);
224 
225   this->BinaryDirectory->setCompleter(new QCMakeFileCompleter(this, true));
226   this->SourceDirectory->setCompleter(new QCMakeFileCompleter(this, true));
227 
228   // fixed pitch font in output window
229   QFont outputFont("Courier");
230   this->Output->setFont(outputFont);
231   this->ErrorFormat.setForeground(QBrush(Qt::red));
232 
233   this->Output->setContextMenuPolicy(Qt::CustomContextMenu);
234   connect(this->Output, &QTextEdit::customContextMenuRequested, this,
235           &CMakeSetupDialog::doOutputContextMenu);
236 
237   // disable open project button
238   this->OpenProjectButton->setDisabled(true);
239 
240   // start the cmake worker thread
241   this->CMakeThread = new QCMakeThread(this);
242   QObject::connect(this->CMakeThread, &QCMakeThread::cmakeInitialized, this,
243                    &CMakeSetupDialog::initialize, Qt::QueuedConnection);
244   this->CMakeThread->start();
245 
246   this->enterState(ReadyConfigure);
247 
248   ProgressOffset = 0.0;
249   ProgressFactor = 1.0;
250 }
251 
initialize()252 void CMakeSetupDialog::initialize()
253 {
254   // now the cmake worker thread is running, lets make our connections to it
255   QObject::connect(this->CMakeThread->cmakeInstance(),
256                    &QCMake::propertiesChanged, this->CacheValues->cacheModel(),
257                    &QCMakeCacheModel::setProperties);
258 
259   QObject::connect(this->ConfigureButton, &QAbstractButton::clicked, this,
260                    &CMakeSetupDialog::doConfigure);
261 
262   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::configureDone,
263                    this, &CMakeSetupDialog::exitLoop);
264   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::generateDone,
265                    this, &CMakeSetupDialog::exitLoop);
266 
267   QObject::connect(this->GenerateButton, &QAbstractButton::clicked, this,
268                    &CMakeSetupDialog::doGenerate);
269   QObject::connect(this->OpenProjectButton, &QAbstractButton::clicked, this,
270                    &CMakeSetupDialog::doOpenProject);
271 
272   QObject::connect(this->BrowseSourceDirectoryButton,
273                    &QAbstractButton::clicked, this,
274                    &CMakeSetupDialog::doSourceBrowse);
275   QObject::connect(this->BrowseBinaryDirectoryButton,
276                    &QAbstractButton::clicked, this,
277                    &CMakeSetupDialog::doBinaryBrowse);
278 
279   QObject::connect(this->BinaryDirectory, &QComboBox::editTextChanged, this,
280                    &CMakeSetupDialog::onBinaryDirectoryChanged);
281   QObject::connect(this->SourceDirectory, &QLineEdit::textChanged, this,
282                    &CMakeSetupDialog::onSourceDirectoryChanged);
283   QObject::connect(this->Preset, &QCMakePresetComboBox::presetChanged, this,
284                    &CMakeSetupDialog::onBuildPresetChanged);
285 
286   QObject::connect(this->CMakeThread->cmakeInstance(),
287                    &QCMake::sourceDirChanged, this,
288                    &CMakeSetupDialog::updateSourceDirectory);
289   QObject::connect(this->CMakeThread->cmakeInstance(),
290                    &QCMake::binaryDirChanged, this,
291                    &CMakeSetupDialog::updateBinaryDirectory);
292   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetsChanged,
293                    this, &CMakeSetupDialog::updatePresets);
294   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetChanged,
295                    this, &CMakeSetupDialog::updatePreset);
296   QObject::connect(this->CMakeThread->cmakeInstance(),
297                    &QCMake::presetLoadError, this,
298                    &CMakeSetupDialog::showPresetLoadError);
299 
300   QObject::connect(this->CMakeThread->cmakeInstance(),
301                    &QCMake::progressChanged, this,
302                    &CMakeSetupDialog::showProgress);
303 
304   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::errorMessage,
305                    this, &CMakeSetupDialog::error);
306 
307   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::outputMessage,
308                    this, &CMakeSetupDialog::message);
309 
310   QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::openPossible,
311                    this->OpenProjectButton, &CMakeSetupDialog::setEnabled);
312 
313   QObject::connect(this->groupedCheck, &QCheckBox::toggled, this,
314                    &CMakeSetupDialog::setGroupedView);
315   QObject::connect(this->advancedCheck, &QCheckBox::toggled, this,
316                    &CMakeSetupDialog::setAdvancedView);
317   QObject::connect(this->Search, &QLineEdit::textChanged, this,
318                    &CMakeSetupDialog::setSearchFilter);
319 
320   QObject::connect(this->CMakeThread->cmakeInstance(),
321                    &QCMake::generatorChanged, this,
322                    &CMakeSetupDialog::updateGeneratorLabel);
323   this->updateGeneratorLabel(QString());
324 
325   QObject::connect(this->CacheValues->cacheModel(),
326                    &QCMakeCacheModel::dataChanged, this,
327                    &CMakeSetupDialog::setCacheModified);
328 
329   QObject::connect(this->CacheValues->selectionModel(),
330                    &QItemSelectionModel::selectionChanged, this,
331                    &CMakeSetupDialog::selectionChanged);
332   QObject::connect(this->RemoveEntry, &QAbstractButton::clicked, this,
333                    &CMakeSetupDialog::removeSelectedCacheEntries);
334   QObject::connect(this->AddEntry, &QAbstractButton::clicked, this,
335                    &CMakeSetupDialog::addCacheEntry);
336 
337   QObject::connect(this->Environment, &QAbstractButton::clicked, this,
338                    &CMakeSetupDialog::editEnvironment);
339 
340   QObject::connect(this->WarnUninitializedAction, &QAction::triggered,
341                    this->CMakeThread->cmakeInstance(),
342                    &QCMake::setWarnUninitializedMode);
343   QObject::connect(this->CMakeThread->cmakeInstance(),
344                    &QCMake::warnUninitializedModeChanged,
345                    this->WarnUninitializedAction, &QAction::setChecked);
346 
347   if (!this->SourceDirectory->text().isEmpty() &&
348       !this->DeferredPreset.isNull()) {
349     this->onSourceDirectoryChanged(this->SourceDirectory->text());
350   } else if (!this->SourceDirectory->text().isEmpty() ||
351              !this->BinaryDirectory->lineEdit()->text().isEmpty()) {
352     this->onSourceDirectoryChanged(this->SourceDirectory->text());
353     this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text());
354   } else {
355     this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text());
356   }
357 
358 #ifdef QT_WINEXTRAS
359   this->TaskbarButton = new QWinTaskbarButton(this);
360   this->TaskbarButton->setWindow(this->windowHandle());
361 #endif
362 }
363 
~CMakeSetupDialog()364 CMakeSetupDialog::~CMakeSetupDialog()
365 {
366   QSettings settings;
367   settings.beginGroup("Settings/StartPath");
368   settings.setValue("windowState", QVariant(saveState()));
369   settings.setValue("geometry", QVariant(saveGeometry()));
370   settings.setValue("SplitterSizes", this->Splitter->saveState());
371 
372   // wait for thread to stop
373   this->CMakeThread->quit();
374   this->CMakeThread->wait();
375 }
376 
prepareConfigure()377 bool CMakeSetupDialog::prepareConfigure()
378 {
379   // make sure build directory exists
380   QString bindir = this->CMakeThread->cmakeInstance()->binaryDirectory();
381   QDir dir(bindir);
382   if (!dir.exists()) {
383     QString msg = tr("Build directory does not exist, "
384                      "should I create it?\n\n"
385                      "Directory: ");
386     msg += bindir;
387     QString title = tr("Create Directory");
388     QMessageBox::StandardButton btn;
389     btn = QMessageBox::information(this, title, msg,
390                                    QMessageBox::Yes | QMessageBox::No);
391     if (btn == QMessageBox::No) {
392       return false;
393     }
394     if (!dir.mkpath(".")) {
395       QMessageBox::information(
396         this, tr("Create Directory Failed"),
397         QString(tr("Failed to create directory %1")).arg(dir.path()),
398         QMessageBox::Ok);
399 
400       return false;
401     }
402   }
403 
404   // if no generator, prompt for it and other setup stuff
405   if (this->CMakeThread->cmakeInstance()->generator().isEmpty()) {
406     if (!this->setupFirstConfigure()) {
407       return false;
408     }
409   }
410 
411   // remember path
412   this->addBinaryPath(dir.absolutePath());
413 
414   return true;
415 }
416 
exitLoop(int err)417 void CMakeSetupDialog::exitLoop(int err)
418 {
419   this->LocalLoop.exit(err);
420 }
421 
doConfigure()422 void CMakeSetupDialog::doConfigure()
423 {
424   if (this->CurrentState == Configuring) {
425     // stop configure
426     doInterrupt();
427     return;
428   }
429 
430   if (!prepareConfigure()) {
431     return;
432   }
433 
434   this->enterState(Configuring);
435 
436   bool ret = doConfigureInternal();
437 
438   if (ret) {
439     this->ConfigureNeeded = false;
440   }
441 
442   if (ret && !this->CacheValues->cacheModel()->newPropertyCount()) {
443     this->enterState(ReadyGenerate);
444   } else {
445     this->enterState(ReadyConfigure);
446     this->CacheValues->scrollToTop();
447   }
448   this->ProgressBar->reset();
449 
450 #ifdef QT_WINEXTRAS
451   this->TaskbarButton->progress()->reset();
452 #endif
453 }
454 
doConfigureInternal()455 bool CMakeSetupDialog::doConfigureInternal()
456 {
457   this->Output->clear();
458   this->CacheValues->selectionModel()->clear();
459 
460   QMetaObject::invokeMethod(
461     this->CMakeThread->cmakeInstance(), "setProperties", Qt::QueuedConnection,
462     Q_ARG(QCMakePropertyList, this->CacheValues->cacheModel()->properties()));
463   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "configure",
464                             Qt::QueuedConnection);
465 
466   int err = this->LocalLoop.exec();
467 
468   if (err != 0) {
469     QMessageBox::critical(
470       this, tr("Error"),
471       tr("Error in configuration process, project files may be invalid"),
472       QMessageBox::Ok);
473   }
474 
475   return 0 == err;
476 }
477 
doInstallForCommandLine()478 void CMakeSetupDialog::doInstallForCommandLine()
479 {
480   QString title = tr("How to Install For Command Line Use");
481   QString msg = tr("One may add CMake to the PATH:\n"
482                    "\n"
483                    " PATH=\"%1\":\"$PATH\"\n"
484                    "\n"
485                    "Or, to install symlinks to '/usr/local/bin', run:\n"
486                    "\n"
487                    " sudo \"%2\" --install\n"
488                    "\n"
489                    "Or, to install symlinks to another directory, run:\n"
490                    "\n"
491                    " sudo \"%3\" --install=/path/to/bin\n");
492   msg = msg.arg(
493     cmSystemTools::GetFilenamePath(cmSystemTools::GetCMakeCommand()).c_str());
494   msg = msg.arg(cmSystemTools::GetCMakeGUICommand().c_str());
495   msg = msg.arg(cmSystemTools::GetCMakeGUICommand().c_str());
496 
497   QDialog dialog;
498   dialog.setWindowTitle(title);
499   QVBoxLayout* l = new QVBoxLayout(&dialog);
500   QLabel* lab = new QLabel(&dialog);
501   l->addWidget(lab);
502   lab->setText(msg);
503   lab->setWordWrap(false);
504   lab->setTextInteractionFlags(Qt::TextSelectableByMouse);
505   QDialogButtonBox* btns =
506     new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog);
507   QObject::connect(btns, &QDialogButtonBox::accepted, &dialog,
508                    &QDialog::accept);
509   l->addWidget(btns);
510   dialog.exec();
511 }
512 
doGenerateInternal()513 bool CMakeSetupDialog::doGenerateInternal()
514 {
515   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "generate",
516                             Qt::QueuedConnection);
517 
518   int err = this->LocalLoop.exec();
519 
520   if (err != 0) {
521     QMessageBox::critical(
522       this, tr("Error"),
523       tr("Error in generation process, project files may be invalid"),
524       QMessageBox::Ok);
525   }
526 
527   return 0 == err;
528 }
529 
doGenerate()530 void CMakeSetupDialog::doGenerate()
531 {
532   if (this->CurrentState == Generating) {
533     // stop generate
534     doInterrupt();
535     return;
536   }
537 
538   // see if we need to configure
539   // we'll need to configure if:
540   //   the configure step hasn't been done yet
541   //   generate was the last step done
542   if (this->ConfigureNeeded) {
543     if (!prepareConfigure()) {
544       return;
545     }
546   }
547 
548   this->enterState(Generating);
549 
550   bool config_passed = true;
551   if (this->ConfigureNeeded) {
552     this->CacheValues->cacheModel()->setShowNewProperties(false);
553     this->ProgressFactor = 0.5;
554     config_passed = doConfigureInternal();
555     this->ProgressOffset = 0.5;
556   }
557 
558   if (config_passed) {
559     doGenerateInternal();
560   }
561 
562   this->ProgressOffset = 0.0;
563   this->ProgressFactor = 1.0;
564   this->CacheValues->cacheModel()->setShowNewProperties(true);
565 
566   this->enterState(ReadyConfigure);
567   this->ProgressBar->reset();
568 #ifdef QT_WINEXTRAS
569   this->TaskbarButton->progress()->reset();
570 #endif
571 
572   this->ConfigureNeeded = true;
573 }
574 
doOpenProject()575 void CMakeSetupDialog::doOpenProject()
576 {
577   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "open",
578                             Qt::QueuedConnection);
579 }
580 
closeEvent(QCloseEvent * e)581 void CMakeSetupDialog::closeEvent(QCloseEvent* e)
582 {
583   // prompt for close if there are unsaved changes, and we're not busy
584   if (this->CacheModified) {
585     QString msg = tr("You have changed options but not rebuilt, "
586                      "are you sure you want to exit?");
587     QString title = tr("Confirm Exit");
588     QMessageBox::StandardButton btn;
589     btn = QMessageBox::critical(this, title, msg,
590                                 QMessageBox::Yes | QMessageBox::No);
591     if (btn == QMessageBox::No) {
592       e->ignore();
593     }
594   }
595 
596   // don't close if we're busy, unless the user really wants to
597   if (this->CurrentState == Configuring) {
598     QString msg =
599       tr("You are in the middle of a Configure.\n"
600          "If you Exit now the configure information will be lost.\n"
601          "Are you sure you want to Exit?");
602     QString title = tr("Confirm Exit");
603     QMessageBox::StandardButton btn;
604     btn = QMessageBox::critical(this, title, msg,
605                                 QMessageBox::Yes | QMessageBox::No);
606     if (btn == QMessageBox::No) {
607       e->ignore();
608     } else {
609       this->doInterrupt();
610     }
611   }
612 
613   // let the generate finish
614   if (this->CurrentState == Generating) {
615     e->ignore();
616   }
617 }
618 
doHelp()619 void CMakeSetupDialog::doHelp()
620 {
621   QString msg = tr(
622     "CMake is used to configure and generate build files for "
623     "software projects.   The basic steps for configuring a project are as "
624     "follows:\r\n\r\n1. Select the source directory for the project.  This "
625     "should "
626     "contain the CMakeLists.txt files for the project.\r\n\r\n2. Select the "
627     "build "
628     "directory for the project.   This is the directory where the project "
629     "will be "
630     "built.  It can be the same or a different directory than the source "
631     "directory.   For easy clean up, a separate build directory is "
632     "recommended. "
633     "CMake will create the directory if it does not exist.\r\n\r\n3. Once the "
634     "source and binary directories are selected, it is time to press the "
635     "Configure button.  This will cause CMake to read all of the input files "
636     "and "
637     "discover all the variables used by the project.   The first time a "
638     "variable "
639     "is displayed it will be in Red.   Users should inspect red variables "
640     "making "
641     "sure the values are correct.   For some projects the Configure process "
642     "can "
643     "be iterative, so continue to press the Configure button until there are "
644     "no "
645     "longer red entries.\r\n\r\n4. Once there are no longer red entries, you "
646     "should click the Generate button.  This will write the build files to "
647     "the build "
648     "directory.");
649 
650   QDialog dialog;
651   QFontMetrics met(this->font());
652 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
653   int msgWidth = met.horizontalAdvance(msg);
654 #else
655   int msgWidth = met.width(msg);
656 #endif
657   dialog.setMinimumSize(msgWidth / 15, 20);
658   dialog.setWindowTitle(tr("Help"));
659   QVBoxLayout* l = new QVBoxLayout(&dialog);
660   QLabel* lab = new QLabel(&dialog);
661   lab->setText(msg);
662   lab->setWordWrap(true);
663   QDialogButtonBox* btns =
664     new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog);
665   QObject::connect(btns, &QDialogButtonBox::accepted, &dialog,
666                    &QDialog::accept);
667   l->addWidget(lab);
668   l->addWidget(btns);
669   dialog.exec();
670 }
671 
doInterrupt()672 void CMakeSetupDialog::doInterrupt()
673 {
674   this->enterState(Interrupting);
675   this->CMakeThread->cmakeInstance()->interrupt();
676 }
677 
doSourceBrowse()678 void CMakeSetupDialog::doSourceBrowse()
679 {
680   QString dir = QFileDialog::getExistingDirectory(
681     this, tr("Enter Path to Source"), this->SourceDirectory->text(),
682     QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
683   if (!dir.isEmpty()) {
684     this->setSourceDirectory(dir);
685   }
686 }
687 
updateSourceDirectory(const QString & dir)688 void CMakeSetupDialog::updateSourceDirectory(const QString& dir)
689 {
690   if (this->SourceDirectory->text() != dir) {
691     this->SourceDirectory->blockSignals(true);
692     this->SourceDirectory->setText(dir);
693     this->SourceDirectory->blockSignals(false);
694   }
695 }
696 
updateBinaryDirectory(const QString & dir)697 void CMakeSetupDialog::updateBinaryDirectory(const QString& dir)
698 {
699   if (this->BinaryDirectory->currentText() != dir) {
700     this->BinaryDirectory->blockSignals(true);
701     this->BinaryDirectory->setEditText(dir);
702     this->BinaryDirectory->blockSignals(false);
703   }
704 }
705 
updatePresets(const QVector<QCMakePreset> & presets)706 void CMakeSetupDialog::updatePresets(const QVector<QCMakePreset>& presets)
707 {
708   if (this->Preset->presets() != presets) {
709     this->Preset->blockSignals(true);
710     this->Preset->setPresets(presets);
711     this->Preset->blockSignals(false);
712   }
713 
714   this->Preset->setDisabled(presets.isEmpty());
715   this->Preset->setToolTip(presets.isEmpty() ? PRESETS_DISABLED_TOOLTIP : "");
716 
717   if (!this->DeferredPreset.isNull()) {
718     this->Preset->setPresetName(this->DeferredPreset);
719     this->DeferredPreset = QString{};
720   }
721 }
722 
updatePreset(const QString & name)723 void CMakeSetupDialog::updatePreset(const QString& name)
724 {
725   if (this->Preset->presetName() != name) {
726     this->Preset->blockSignals(true);
727     this->Preset->setPresetName(name);
728     this->Preset->blockSignals(false);
729   }
730 }
731 
showPresetLoadError(const QString & dir,cmCMakePresetsFile::ReadFileResult result)732 void CMakeSetupDialog::showPresetLoadError(
733   const QString& dir, cmCMakePresetsFile::ReadFileResult result)
734 {
735   QMessageBox::warning(
736     this, "Error Reading CMake Presets",
737     QString::fromLocal8Bit("Could not read presets from %1: %2")
738       .arg(dir, cmCMakePresetsFile::ResultToString(result)));
739 }
740 
doBinaryBrowse()741 void CMakeSetupDialog::doBinaryBrowse()
742 {
743   QString dir = QFileDialog::getExistingDirectory(
744     this, tr("Enter Path to Build"), this->BinaryDirectory->currentText(),
745     QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
746   if (!dir.isEmpty() && dir != this->BinaryDirectory->currentText()) {
747     this->setBinaryDirectory(dir);
748   }
749 }
750 
setBinaryDirectory(const QString & dir)751 void CMakeSetupDialog::setBinaryDirectory(const QString& dir)
752 {
753   this->BinaryDirectory->setEditText(dir);
754 }
755 
setStartupBinaryDirectory(bool startup)756 void CMakeSetupDialog::setStartupBinaryDirectory(bool startup)
757 {
758   this->StartupBinaryDirectory = startup;
759 }
760 
onSourceDirectoryChanged(const QString & dir)761 void CMakeSetupDialog::onSourceDirectoryChanged(const QString& dir)
762 {
763   this->Output->clear();
764   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
765                             "setSourceDirectory", Qt::QueuedConnection,
766                             Q_ARG(QString, dir));
767 }
768 
onBinaryDirectoryChanged(const QString & dir)769 void CMakeSetupDialog::onBinaryDirectoryChanged(const QString& dir)
770 {
771   QString title = QString(tr("CMake %1 - %2"));
772   title = title.arg(cmVersion::GetCMakeVersion());
773   title = title.arg(dir);
774   this->setWindowTitle(title);
775 
776   this->CacheModified = false;
777   this->CacheValues->cacheModel()->clear();
778   qobject_cast<QCMakeCacheModelDelegate*>(this->CacheValues->itemDelegate())
779     ->clearChanges();
780   this->Output->clear();
781   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
782                             "setBinaryDirectory", Qt::QueuedConnection,
783                             Q_ARG(QString, dir));
784 }
785 
onBuildPresetChanged(const QString & name)786 void CMakeSetupDialog::onBuildPresetChanged(const QString& name)
787 {
788   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setPreset",
789                             Qt::QueuedConnection, Q_ARG(QString, name),
790                             Q_ARG(bool, !this->StartupBinaryDirectory));
791   this->StartupBinaryDirectory = false;
792 }
793 
setSourceDirectory(const QString & dir)794 void CMakeSetupDialog::setSourceDirectory(const QString& dir)
795 {
796   this->SourceDirectory->setText(dir);
797 }
798 
setDeferredPreset(const QString & preset)799 void CMakeSetupDialog::setDeferredPreset(const QString& preset)
800 {
801   this->DeferredPreset = preset;
802 }
803 
showProgress(const QString &,float percent)804 void CMakeSetupDialog::showProgress(const QString& /*msg*/, float percent)
805 {
806   percent = (percent * ProgressFactor) + ProgressOffset;
807   this->ProgressBar->setValue(qRound(percent * 100));
808 
809 #ifdef QT_WINEXTRAS
810   QWinTaskbarProgress* progress = this->TaskbarButton->progress();
811   progress->setVisible(true);
812   progress->setValue(qRound(percent * 100));
813 #endif
814 }
815 
error(const QString & msg)816 void CMakeSetupDialog::error(const QString& msg)
817 {
818   this->Output->setCurrentCharFormat(this->ErrorFormat);
819   // QTextEdit will terminate the msg with a ParagraphSeparator, but it also
820   // replaces
821   // all newlines with ParagraphSeparators. By replacing the newlines by
822   // ourself, one
823   // error msg will be one paragraph.
824   QString paragraph(msg);
825   paragraph.replace(QLatin1Char('\n'), QChar::LineSeparator);
826   this->Output->append(paragraph);
827 }
828 
message(const QString & msg)829 void CMakeSetupDialog::message(const QString& msg)
830 {
831   this->Output->setCurrentCharFormat(this->MessageFormat);
832   this->Output->append(msg);
833 }
834 
setEnabledState(bool enabled)835 void CMakeSetupDialog::setEnabledState(bool enabled)
836 {
837   // disable parts of the GUI during configure/generate
838   this->CacheValues->cacheModel()->setEditEnabled(enabled);
839   this->SourceDirectory->setEnabled(enabled);
840   this->BrowseSourceDirectoryButton->setEnabled(enabled);
841   this->Preset->setEnabled(enabled && !this->Preset->presets().isEmpty());
842   this->BinaryDirectory->setEnabled(enabled);
843   this->BrowseBinaryDirectoryButton->setEnabled(enabled);
844   this->ReloadCacheAction->setEnabled(enabled);
845   this->DeleteCacheAction->setEnabled(enabled);
846   this->ExitAction->setEnabled(enabled);
847   this->ConfigureAction->setEnabled(enabled);
848   this->AddEntry->setEnabled(enabled);
849   this->RemoveEntry->setEnabled(false); // let selection re-enable it
850   this->Environment->setEnabled(enabled);
851 }
852 
setupFirstConfigure()853 bool CMakeSetupDialog::setupFirstConfigure()
854 {
855   FirstConfigure dialog;
856 
857   // initialize dialog and restore saved settings
858 
859   // add generators
860   dialog.setGenerators(
861     this->CMakeThread->cmakeInstance()->availableGenerators());
862 
863   // restore from settings
864   dialog.loadFromSettings();
865 
866   auto presetData = this->Preset->currentData();
867   if (presetData.isValid()) {
868     auto preset = presetData.value<QCMakePreset>();
869     dialog.setCurrentGenerator(preset.generator);
870     if (preset.setArchitecture) {
871       dialog.setPlatform(preset.architecture);
872     }
873     if (preset.setToolset) {
874       dialog.setToolset(preset.toolset);
875     }
876     dialog.setCompilerOption(CompilerOption::DefaultNative);
877   }
878 
879   if (dialog.exec() == QDialog::Accepted) {
880     dialog.saveToSettings();
881     this->CMakeThread->cmakeInstance()->setGenerator(dialog.getGenerator());
882     this->CMakeThread->cmakeInstance()->setPlatform(dialog.getPlatform());
883     this->CMakeThread->cmakeInstance()->setToolset(dialog.getToolset());
884 
885     QCMakeCacheModel* m = this->CacheValues->cacheModel();
886 
887     if (dialog.compilerSetup()) {
888       QString fortranCompiler = dialog.getFortranCompiler();
889       if (!fortranCompiler.isEmpty()) {
890         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER",
891                           "Fortran compiler.", fortranCompiler, false);
892       }
893       QString cxxCompiler = dialog.getCXXCompiler();
894       if (!cxxCompiler.isEmpty()) {
895         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER",
896                           "CXX compiler.", cxxCompiler, false);
897       }
898 
899       QString cCompiler = dialog.getCCompiler();
900       if (!cCompiler.isEmpty()) {
901         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER",
902                           "C compiler.", cCompiler, false);
903       }
904     } else if (dialog.crossCompilerSetup()) {
905       QString fortranCompiler = dialog.getFortranCompiler();
906       if (!fortranCompiler.isEmpty()) {
907         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER",
908                           "Fortran compiler.", fortranCompiler, false);
909       }
910 
911       QString mode = dialog.getCrossIncludeMode();
912       m->insertProperty(QCMakeProperty::STRING,
913                         "CMAKE_FIND_ROOT_PATH_MODE_INCLUDE",
914                         tr("CMake Find Include Mode"), mode, false);
915       mode = dialog.getCrossLibraryMode();
916       m->insertProperty(QCMakeProperty::STRING,
917                         "CMAKE_FIND_ROOT_PATH_MODE_LIBRARY",
918                         tr("CMake Find Library Mode"), mode, false);
919       mode = dialog.getCrossProgramMode();
920       m->insertProperty(QCMakeProperty::STRING,
921                         "CMAKE_FIND_ROOT_PATH_MODE_PROGRAM",
922                         tr("CMake Find Program Mode"), mode, false);
923 
924       QString rootPath = dialog.getCrossRoot();
925       m->insertProperty(QCMakeProperty::PATH, "CMAKE_FIND_ROOT_PATH",
926                         tr("CMake Find Root Path"), rootPath, false);
927 
928       QString systemName = dialog.getSystemName();
929       m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_NAME",
930                         tr("CMake System Name"), systemName, false);
931       QString systemVersion = dialog.getSystemVersion();
932       m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_VERSION",
933                         tr("CMake System Version"), systemVersion, false);
934       QString systemProcessor = dialog.getSystemProcessor();
935       m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_PROCESSOR",
936                         tr("CMake System Processor"), systemProcessor, false);
937       QString cxxCompiler = dialog.getCXXCompiler();
938       if (!cxxCompiler.isEmpty()) {
939         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER",
940                           tr("CXX compiler."), cxxCompiler, false);
941       }
942       QString cCompiler = dialog.getCCompiler();
943       if (!cCompiler.isEmpty()) {
944         m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER",
945                           tr("C compiler."), cCompiler, false);
946       }
947     } else if (dialog.crossCompilerToolChainFile()) {
948       QString toolchainFile = dialog.getCrossCompilerToolChainFile();
949       m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_TOOLCHAIN_FILE",
950                         tr("Cross Compile ToolChain File"), toolchainFile,
951                         false);
952     }
953     return true;
954   }
955 
956   return false;
957 }
958 
updateGeneratorLabel(const QString & gen)959 void CMakeSetupDialog::updateGeneratorLabel(const QString& gen)
960 {
961   QString str = tr("Current Generator: ");
962   if (gen.isEmpty()) {
963     str += tr("None");
964   } else {
965     str += gen;
966   }
967   this->Generator->setText(str);
968 }
969 
doReloadCache()970 void CMakeSetupDialog::doReloadCache()
971 {
972   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "reloadCache",
973                             Qt::QueuedConnection);
974 }
975 
doDeleteCache()976 void CMakeSetupDialog::doDeleteCache()
977 {
978   QString title = tr("Delete Cache");
979   QString msg = tr("Are you sure you want to delete the cache?");
980   QMessageBox::StandardButton btn;
981   btn = QMessageBox::information(this, title, msg,
982                                  QMessageBox::Yes | QMessageBox::No);
983   if (btn == QMessageBox::No) {
984     return;
985   }
986   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "deleteCache",
987                             Qt::QueuedConnection);
988 }
989 
doAbout()990 void CMakeSetupDialog::doAbout()
991 {
992   QString msg = tr(
993     "CMake %1 (cmake.org).\n"
994     "CMake suite maintained and supported by Kitware (kitware.com/cmake).\n"
995     "Distributed under terms of the BSD 3-Clause License.\n"
996     "\n"
997     "CMake GUI maintained by csimsoft,\n"
998     "built using Qt %2 (qt-project.org).\n"
999 #ifdef USE_LGPL
1000     "\n"
1001     "The Qt Toolkit is Copyright (C) The Qt Company Ltd.\n"
1002     "Qt is licensed under terms of the GNU LGPLv" USE_LGPL ", available at:\n"
1003     " \"%3\""
1004 #endif
1005   );
1006   msg = msg.arg(cmVersion::GetCMakeVersion());
1007   msg = msg.arg(qVersion());
1008 #ifdef USE_LGPL
1009   std::string lgpl =
1010     cmSystemTools::GetCMakeRoot() + "/Licenses/LGPLv" USE_LGPL ".txt";
1011   msg = msg.arg(lgpl.c_str());
1012 #endif
1013 
1014   QDialog dialog;
1015   dialog.setWindowTitle(tr("About"));
1016   QVBoxLayout* l = new QVBoxLayout(&dialog);
1017   QLabel* lab = new QLabel(&dialog);
1018   l->addWidget(lab);
1019   lab->setText(msg);
1020   lab->setWordWrap(true);
1021   QDialogButtonBox* btns =
1022     new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog);
1023   QObject::connect(btns, &QDialogButtonBox::accepted, &dialog,
1024                    &QDialog::accept);
1025   l->addWidget(btns);
1026   dialog.exec();
1027 }
1028 
setExitAfterGenerate(bool b)1029 void CMakeSetupDialog::setExitAfterGenerate(bool b)
1030 {
1031   this->ExitAfterGenerate = b;
1032 }
1033 
addBinaryPath(const QString & path)1034 void CMakeSetupDialog::addBinaryPath(const QString& path)
1035 {
1036   QString cleanpath = QDir::cleanPath(path);
1037 
1038   // update UI
1039   this->BinaryDirectory->blockSignals(true);
1040   int idx = this->BinaryDirectory->findText(cleanpath);
1041   if (idx != -1) {
1042     this->BinaryDirectory->removeItem(idx);
1043   }
1044   this->BinaryDirectory->insertItem(0, cleanpath);
1045   this->BinaryDirectory->setCurrentIndex(0);
1046   this->BinaryDirectory->blockSignals(false);
1047 
1048   // save to registry
1049   QStringList buildPaths = this->loadBuildPaths();
1050   buildPaths.removeAll(cleanpath);
1051   buildPaths.prepend(cleanpath);
1052   this->saveBuildPaths(buildPaths);
1053 }
1054 
dragEnterEvent(QDragEnterEvent * e)1055 void CMakeSetupDialog::dragEnterEvent(QDragEnterEvent* e)
1056 {
1057   if (!(this->CurrentState == ReadyConfigure ||
1058         this->CurrentState == ReadyGenerate)) {
1059     e->ignore();
1060     return;
1061   }
1062 
1063   const QMimeData* dat = e->mimeData();
1064   QList<QUrl> urls = dat->urls();
1065   QString file = urls.count() ? urls[0].toLocalFile() : QString();
1066   if (!file.isEmpty() &&
1067       (file.endsWith("CMakeCache.txt", Qt::CaseInsensitive) ||
1068        file.endsWith("CMakeLists.txt", Qt::CaseInsensitive))) {
1069     e->accept();
1070   } else {
1071     e->ignore();
1072   }
1073 }
1074 
dropEvent(QDropEvent * e)1075 void CMakeSetupDialog::dropEvent(QDropEvent* e)
1076 {
1077   if (!(this->CurrentState == ReadyConfigure ||
1078         this->CurrentState == ReadyGenerate)) {
1079     return;
1080   }
1081 
1082   const QMimeData* dat = e->mimeData();
1083   QList<QUrl> urls = dat->urls();
1084   QString file = urls.count() ? urls[0].toLocalFile() : QString();
1085   if (file.endsWith("CMakeCache.txt", Qt::CaseInsensitive)) {
1086     QFileInfo info(file);
1087     if (this->CMakeThread->cmakeInstance()->binaryDirectory() !=
1088         info.absolutePath()) {
1089       this->setBinaryDirectory(info.absolutePath());
1090     }
1091   } else if (file.endsWith("CMakeLists.txt", Qt::CaseInsensitive)) {
1092     QFileInfo info(file);
1093     if (this->CMakeThread->cmakeInstance()->binaryDirectory() !=
1094         info.absolutePath()) {
1095       this->setSourceDirectory(info.absolutePath());
1096       this->setBinaryDirectory(info.absolutePath());
1097     }
1098   }
1099 }
1100 
loadBuildPaths()1101 QStringList CMakeSetupDialog::loadBuildPaths()
1102 {
1103   QSettings settings;
1104   settings.beginGroup("Settings/StartPath");
1105 
1106   QStringList buildPaths;
1107   for (int i = 0; i < 10; i++) {
1108     QString p = settings.value(QString("WhereBuild%1").arg(i)).toString();
1109     if (!p.isEmpty()) {
1110       buildPaths.append(p);
1111     }
1112   }
1113   return buildPaths;
1114 }
1115 
saveBuildPaths(const QStringList & paths)1116 void CMakeSetupDialog::saveBuildPaths(const QStringList& paths)
1117 {
1118   QSettings settings;
1119   settings.beginGroup("Settings/StartPath");
1120 
1121   int num = paths.count();
1122   if (num > 10) {
1123     num = 10;
1124   }
1125 
1126   for (int i = 0; i < num; i++) {
1127     settings.setValue(QString("WhereBuild%1").arg(i), paths[i]);
1128   }
1129 }
1130 
setCacheModified()1131 void CMakeSetupDialog::setCacheModified()
1132 {
1133   this->CacheModified = true;
1134   this->ConfigureNeeded = true;
1135   this->enterState(ReadyConfigure);
1136 }
1137 
removeSelectedCacheEntries()1138 void CMakeSetupDialog::removeSelectedCacheEntries()
1139 {
1140   QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows();
1141   QList<QPersistentModelIndex> pidxs;
1142   foreach (QModelIndex const& i, idxs) {
1143     pidxs.append(i);
1144   }
1145   foreach (QPersistentModelIndex const& pi, pidxs) {
1146     this->CacheValues->model()->removeRow(pi.row(), pi.parent());
1147   }
1148 }
1149 
selectionChanged()1150 void CMakeSetupDialog::selectionChanged()
1151 {
1152   QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows();
1153   if (idxs.count() &&
1154       (this->CurrentState == ReadyConfigure ||
1155        this->CurrentState == ReadyGenerate)) {
1156     this->RemoveEntry->setEnabled(true);
1157   } else {
1158     this->RemoveEntry->setEnabled(false);
1159   }
1160 }
1161 
enterState(CMakeSetupDialog::State s)1162 void CMakeSetupDialog::enterState(CMakeSetupDialog::State s)
1163 {
1164   if (s == this->CurrentState) {
1165     return;
1166   }
1167 
1168   this->CurrentState = s;
1169 
1170   if (s == Interrupting) {
1171     this->ConfigureButton->setEnabled(false);
1172     this->GenerateButton->setEnabled(false);
1173     this->OpenProjectButton->setEnabled(false);
1174   } else if (s == Configuring) {
1175     this->setEnabledState(false);
1176     this->GenerateButton->setEnabled(false);
1177     this->GenerateAction->setEnabled(false);
1178     this->OpenProjectButton->setEnabled(false);
1179     this->ConfigureButton->setText(tr("&Stop"));
1180   } else if (s == Generating) {
1181     this->CacheModified = false;
1182     this->setEnabledState(false);
1183     this->ConfigureButton->setEnabled(false);
1184     this->GenerateAction->setEnabled(false);
1185     this->OpenProjectButton->setEnabled(false);
1186     this->GenerateButton->setText(tr("&Stop"));
1187   } else if (s == ReadyConfigure || s == ReadyGenerate) {
1188     this->setEnabledState(true);
1189     this->GenerateButton->setEnabled(true);
1190     this->GenerateAction->setEnabled(true);
1191     this->ConfigureButton->setEnabled(true);
1192     this->ConfigureButton->setText(tr("&Configure"));
1193     this->GenerateButton->setText(tr("&Generate"));
1194   }
1195 }
1196 
editEnvironment()1197 void CMakeSetupDialog::editEnvironment()
1198 {
1199   EnvironmentDialog dialog(this->CMakeThread->cmakeInstance()->environment(),
1200                            this);
1201   if (dialog.exec() == QDialog::Accepted) {
1202     QMetaObject::invokeMethod(
1203       this->CMakeThread->cmakeInstance(), "setEnvironment",
1204       Q_ARG(QProcessEnvironment, dialog.environment()));
1205   }
1206 }
1207 
addCacheEntry()1208 void CMakeSetupDialog::addCacheEntry()
1209 {
1210   QDialog dialog(this);
1211   dialog.resize(400, 200);
1212   dialog.setWindowTitle(tr("Add Cache Entry"));
1213   QVBoxLayout* l = new QVBoxLayout(&dialog);
1214   AddCacheEntry* w =
1215     new AddCacheEntry(&dialog, this->AddVariableNames, this->AddVariableTypes);
1216   QDialogButtonBox* btns = new QDialogButtonBox(
1217     QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
1218   QObject::connect(btns, &QDialogButtonBox::accepted, &dialog,
1219                    &QDialog::accept);
1220   QObject::connect(btns, &QDialogButtonBox::rejected, &dialog,
1221                    &QDialog::reject);
1222   l->addWidget(w);
1223   l->addStretch();
1224   l->addWidget(btns);
1225   if (QDialog::Accepted == dialog.exec()) {
1226     QCMakeCacheModel* m = this->CacheValues->cacheModel();
1227     m->insertProperty(w->type(), w->name(), w->description(), w->value(),
1228                       false);
1229 
1230     // only add variable names to the completion which are new
1231     if (!this->AddVariableNames.contains(w->name())) {
1232       this->AddVariableNames << w->name();
1233       this->AddVariableTypes << w->typeString();
1234       // limit to at most 100 completion items
1235       if (this->AddVariableNames.size() > 100) {
1236         this->AddVariableNames.removeFirst();
1237         this->AddVariableTypes.removeFirst();
1238       }
1239       // make sure CMAKE_INSTALL_PREFIX is always there
1240       if (!this->AddVariableNames.contains("CMAKE_INSTALL_PREFIX")) {
1241         this->AddVariableNames << "CMAKE_INSTALL_PREFIX";
1242         this->AddVariableTypes << "PATH";
1243       }
1244       QSettings settings;
1245       settings.beginGroup("Settings/StartPath");
1246       settings.setValue("AddVariableNames", this->AddVariableNames);
1247       settings.setValue("AddVariableTypes", this->AddVariableTypes);
1248     }
1249   }
1250 }
1251 
startSearch()1252 void CMakeSetupDialog::startSearch()
1253 {
1254   this->Search->setFocus(Qt::OtherFocusReason);
1255   this->Search->selectAll();
1256 }
1257 
setDebugOutput(bool flag)1258 void CMakeSetupDialog::setDebugOutput(bool flag)
1259 {
1260   QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
1261                             "setDebugOutput", Qt::QueuedConnection,
1262                             Q_ARG(bool, flag));
1263 }
1264 
setGroupedView(bool v)1265 void CMakeSetupDialog::setGroupedView(bool v)
1266 {
1267   this->CacheValues->cacheModel()->setViewType(v ? QCMakeCacheModel::GroupView
1268                                                  : QCMakeCacheModel::FlatView);
1269   this->CacheValues->setRootIsDecorated(v);
1270 
1271   QSettings settings;
1272   settings.beginGroup("Settings/StartPath");
1273   settings.setValue("GroupView", v);
1274 }
1275 
setAdvancedView(bool v)1276 void CMakeSetupDialog::setAdvancedView(bool v)
1277 {
1278   this->CacheValues->setShowAdvanced(v);
1279   QSettings settings;
1280   settings.beginGroup("Settings/StartPath");
1281   settings.setValue("AdvancedView", v);
1282 }
1283 
showUserChanges()1284 void CMakeSetupDialog::showUserChanges()
1285 {
1286   QSet<QCMakeProperty> changes =
1287     qobject_cast<QCMakeCacheModelDelegate*>(this->CacheValues->itemDelegate())
1288       ->changes();
1289 
1290   QDialog dialog(this);
1291   dialog.setWindowTitle(tr("My Changes"));
1292   dialog.resize(600, 400);
1293   QVBoxLayout* l = new QVBoxLayout(&dialog);
1294   QTextEdit* textedit = new QTextEdit(&dialog);
1295   textedit->setReadOnly(true);
1296   l->addWidget(textedit);
1297   QDialogButtonBox* btns =
1298     new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &dialog);
1299   QObject::connect(btns, &QDialogButtonBox::rejected, &dialog,
1300                    &QDialog::accept);
1301   l->addWidget(btns);
1302 
1303   QString command;
1304   QString cache;
1305 
1306   foreach (QCMakeProperty const& prop, changes) {
1307     QString type;
1308     switch (prop.Type) {
1309       case QCMakeProperty::BOOL:
1310         type = "BOOL";
1311         break;
1312       case QCMakeProperty::PATH:
1313         type = "PATH";
1314         break;
1315       case QCMakeProperty::FILEPATH:
1316         type = "FILEPATH";
1317         break;
1318       case QCMakeProperty::STRING:
1319         type = "STRING";
1320         break;
1321     }
1322     QString value;
1323     if (prop.Type == QCMakeProperty::BOOL) {
1324       value = prop.Value.toBool() ? "1" : "0";
1325     } else {
1326       value = prop.Value.toString();
1327     }
1328 
1329     QString const line = QString("%1:%2=").arg(prop.Key, type);
1330     command += QString("-D%1\"%2\" ").arg(line, value);
1331     cache += QString("%1%2\n").arg(line, value);
1332   }
1333 
1334   textedit->append(tr("Commandline options:"));
1335   textedit->append(command);
1336   textedit->append("\n");
1337   textedit->append(tr("Cache file:"));
1338   textedit->append(cache);
1339 
1340   dialog.exec();
1341 }
1342 
setSearchFilter(const QString & str)1343 void CMakeSetupDialog::setSearchFilter(const QString& str)
1344 {
1345   this->CacheValues->selectionModel()->clear();
1346   this->CacheValues->setSearchFilter(str);
1347 }
1348 
doOutputContextMenu(QPoint pt)1349 void CMakeSetupDialog::doOutputContextMenu(QPoint pt)
1350 {
1351   std::unique_ptr<QMenu> menu(this->Output->createStandardContextMenu());
1352 
1353   menu->addSeparator();
1354   auto* a = menu->addAction(tr("Find..."));
1355   QObject::connect(a, &QAction::triggered, this,
1356                    &CMakeSetupDialog::doOutputFindDialog);
1357   a->setShortcut(QKeySequence::Find);
1358   a = menu->addAction(tr("Find Next"));
1359   QObject::connect(a, &QAction::triggered, this,
1360                    &CMakeSetupDialog::doOutputFindNext);
1361   a->setShortcut(QKeySequence::FindNext);
1362   a = menu->addAction(tr("Find Previous"));
1363   QObject::connect(a, &QAction::triggered, this,
1364                    &CMakeSetupDialog::doOutputFindPrev);
1365   a->setShortcut(QKeySequence::FindPrevious);
1366   menu->addSeparator();
1367   a = menu->addAction(tr("Goto Next Error"));
1368   QObject::connect(a, &QAction::triggered, this,
1369                    &CMakeSetupDialog::doOutputErrorNext);
1370   a->setShortcut(QKeySequence(Qt::Key_F8));
1371 
1372   menu->exec(this->Output->mapToGlobal(pt));
1373 }
1374 
doOutputFindDialog()1375 void CMakeSetupDialog::doOutputFindDialog()
1376 {
1377   QStringList strings(this->FindHistory);
1378 
1379   QString selection = this->Output->textCursor().selectedText();
1380   if (!selection.isEmpty() && !selection.contains(QChar::ParagraphSeparator) &&
1381       !selection.contains(QChar::LineSeparator)) {
1382     strings.push_front(selection);
1383   }
1384 
1385   bool ok;
1386   QString search = QInputDialog::getItem(this, tr("Find in Output"),
1387                                          tr("Find:"), strings, 0, true, &ok);
1388   if (ok && !search.isEmpty()) {
1389     if (!this->FindHistory.contains(search)) {
1390       this->FindHistory.push_front(search);
1391     }
1392     doOutputFindNext();
1393   }
1394 }
1395 
doRegexExplorerDialog()1396 void CMakeSetupDialog::doRegexExplorerDialog()
1397 {
1398   RegexExplorer dialog(this);
1399   dialog.exec();
1400 }
1401 
doOutputFindPrev()1402 void CMakeSetupDialog::doOutputFindPrev()
1403 {
1404   doOutputFindNext(false);
1405 }
1406 
doOutputFindNext(bool directionForward)1407 void CMakeSetupDialog::doOutputFindNext(bool directionForward)
1408 {
1409   if (this->FindHistory.isEmpty()) {
1410     doOutputFindDialog(); // will re-call this function again
1411     return;
1412   }
1413 
1414   QString search = this->FindHistory.front();
1415 
1416   QTextCursor textCursor = this->Output->textCursor();
1417   QTextDocument* document = this->Output->document();
1418   QTextDocument::FindFlags flags;
1419   if (!directionForward) {
1420     flags |= QTextDocument::FindBackward;
1421   }
1422 
1423   textCursor = document->find(search, textCursor, flags);
1424 
1425   if (textCursor.isNull()) {
1426     // first search found nothing, wrap around and search again
1427     textCursor = this->Output->textCursor();
1428     textCursor.movePosition(directionForward ? QTextCursor::Start
1429                                              : QTextCursor::End);
1430     textCursor = document->find(search, textCursor, flags);
1431   }
1432 
1433   if (textCursor.hasSelection()) {
1434     this->Output->setTextCursor(textCursor);
1435   }
1436 }
1437 
doOutputErrorNext()1438 void CMakeSetupDialog::doOutputErrorNext()
1439 {
1440   QTextCursor textCursor = this->Output->textCursor();
1441   bool atEnd = false;
1442 
1443   // move cursor out of current error-block
1444   if (textCursor.blockCharFormat() == this->ErrorFormat) {
1445     atEnd = !textCursor.movePosition(QTextCursor::NextBlock);
1446   }
1447 
1448   // move cursor to next error-block
1449   while (textCursor.blockCharFormat() != this->ErrorFormat && !atEnd) {
1450     atEnd = !textCursor.movePosition(QTextCursor::NextBlock);
1451   }
1452 
1453   if (atEnd) {
1454     // first search found nothing, wrap around and search again
1455     atEnd = !textCursor.movePosition(QTextCursor::Start);
1456 
1457     // move cursor to next error-block
1458     while (textCursor.blockCharFormat() != this->ErrorFormat && !atEnd) {
1459       atEnd = !textCursor.movePosition(QTextCursor::NextBlock);
1460     }
1461   }
1462 
1463   if (!atEnd) {
1464     textCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1465 
1466     QTextCharFormat selectionFormat;
1467     selectionFormat.setBackground(Qt::yellow);
1468     QTextEdit::ExtraSelection extraSelection = { textCursor, selectionFormat };
1469     this->Output->setExtraSelections(QList<QTextEdit::ExtraSelection>()
1470                                      << extraSelection);
1471 
1472     // make the whole error-block visible
1473     this->Output->setTextCursor(textCursor);
1474 
1475     // remove the selection to see the extraSelection
1476     textCursor.setPosition(textCursor.anchor());
1477     this->Output->setTextCursor(textCursor);
1478   }
1479 }
1480 
doWarningMessagesDialog()1481 void CMakeSetupDialog::doWarningMessagesDialog()
1482 {
1483   WarningMessagesDialog dialog(this, this->CMakeThread->cmakeInstance());
1484   dialog.exec();
1485 }
1486