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