1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "toolchainoptionspage.h"
27 #include "toolchain.h"
28 #include "abi.h"
29 #include "projectexplorerconstants.h"
30 #include "projectexplorericons.h"
31 #include "toolchainconfigwidget.h"
32 #include "toolchainmanager.h"
33
34 #include <app/app_version.h>
35 #include <coreplugin/icore.h>
36 #include <extensionsystem/pluginmanager.h>
37
38 #include <utils/algorithm.h>
39 #include <utils/detailswidget.h>
40 #include <utils/qtcassert.h>
41 #include <utils/treemodel.h>
42 #include <utils/utilsicons.h>
43
44 #include <QAction>
45 #include <QApplication>
46 #include <QCheckBox>
47 #include <QCoreApplication>
48 #include <QDialog>
49 #include <QDialogButtonBox>
50 #include <QHBoxLayout>
51 #include <QHeaderView>
52 #include <QItemSelectionModel>
53 #include <QMenu>
54 #include <QMessageBox>
55 #include <QPushButton>
56 #include <QSet>
57 #include <QSpacerItem>
58 #include <QStackedWidget>
59 #include <QTextStream>
60 #include <QTreeView>
61 #include <QVBoxLayout>
62
63 using namespace Utils;
64
65 namespace ProjectExplorer {
66 namespace Internal {
67
68 class ToolChainTreeItem : public TreeItem
69 {
70 public:
ToolChainTreeItem(QStackedWidget * parentWidget,ToolChain * tc,bool c)71 ToolChainTreeItem(QStackedWidget *parentWidget, ToolChain *tc, bool c) :
72 toolChain(tc), changed(c)
73 {
74 widget = tc->createConfigurationWidget().release();
75 if (widget) {
76 parentWidget->addWidget(widget);
77 if (tc->isAutoDetected())
78 widget->makeReadOnly();
79 QObject::connect(widget, &ToolChainConfigWidget::dirty,
80 [this] {
81 changed = true;
82 update();
83 });
84 }
85 }
86
data(int column,int role) const87 QVariant data(int column, int role) const override
88 {
89 switch (role) {
90 case Qt::DisplayRole:
91 if (column == 0)
92 return toolChain->displayName();
93 return toolChain->typeDisplayName();
94 case Qt::FontRole: {
95 QFont font;
96 font.setBold(changed);
97 return font;
98 }
99 case Qt::ToolTipRole:
100 if (!toolChain->isValid())
101 return ToolChainOptionsPage::tr("This toolchain is invalid.");
102 return ToolChainOptionsPage::tr("<nobr><b>ABI:</b> %1").arg(
103 changed ? ToolChainOptionsPage::tr("not up-to-date")
104 : toolChain->targetAbi().toString());
105 case Qt::DecorationRole:
106 return column == 0 && !toolChain->isValid()
107 ? Utils::Icons::CRITICAL.icon() : QVariant();
108 }
109 return QVariant();
110 }
111
112 ToolChain *toolChain;
113 ToolChainConfigWidget *widget;
114 bool changed;
115 };
116
117 class DetectionSettingsDialog : public QDialog
118 {
119 public:
DetectionSettingsDialog(const ToolchainDetectionSettings & settings,QWidget * parent)120 DetectionSettingsDialog(const ToolchainDetectionSettings &settings, QWidget *parent)
121 : QDialog(parent)
122 {
123 setWindowTitle(ToolChainOptionsPage::tr("Toolchain Auto-detection Settings"));
124 const auto layout = new QVBoxLayout(this);
125 m_detectX64AsX32CheckBox.setText(ToolChainOptionsPage::tr("Detect x86_64 GCC compilers "
126 "as x86_64 and x86"));
127 m_detectX64AsX32CheckBox.setToolTip(ToolChainOptionsPage::tr("If checked, %1 will "
128 "set up two instances of each x86_64 compiler:\nOne for the native x86_64 target, and "
129 "one for a plain x86 target.\nEnable this if you plan to create 32-bit x86 binaries "
130 "without using a dedicated cross compiler.").arg(Core::Constants::IDE_DISPLAY_NAME));
131 m_detectX64AsX32CheckBox.setChecked(settings.detectX64AsX32);
132 layout->addWidget(&m_detectX64AsX32CheckBox);
133 const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
134 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
135 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
136 layout->addWidget(buttonBox);
137 }
138
settings() const139 ToolchainDetectionSettings settings() const
140 {
141 ToolchainDetectionSettings s;
142 s.detectX64AsX32 = m_detectX64AsX32CheckBox.isChecked();
143 return s;
144 }
145
146 private:
147 QCheckBox m_detectX64AsX32CheckBox;
148 };
149
150 // --------------------------------------------------------------------------
151 // ToolChainOptionsWidget
152 // --------------------------------------------------------------------------
153
154 class ToolChainOptionsWidget final : public Core::IOptionsPageWidget
155 {
156 public:
ToolChainOptionsWidget()157 ToolChainOptionsWidget()
158 {
159 m_detectionSettings = ToolChainManager::detectionSettings();
160 m_factories = Utils::filtered(ToolChainFactory::allToolChainFactories(),
161 [](ToolChainFactory *factory) { return factory->canCreate();});
162
163 m_model.setHeader({ToolChainOptionsPage::tr("Name"), ToolChainOptionsPage::tr("Type")});
164 auto autoRoot = new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()},
165 {ProjectExplorer::Constants::msgAutoDetectedToolTip()});
166 auto manualRoot = new StaticTreeItem(ProjectExplorer::Constants::msgManual());
167
168 foreach (const Utils::Id &l, ToolChainManager::allLanguages()) {
169 const QString dn = ToolChainManager::displayNameOfLanguageId(l);
170 auto autoNode = new StaticTreeItem(dn);
171 auto manualNode = new StaticTreeItem(dn);
172
173 autoRoot->appendChild(autoNode);
174 manualRoot->appendChild(manualNode);
175
176 m_languageMap.insert(l, qMakePair(autoNode, manualNode));
177 }
178
179 m_model.rootItem()->appendChild(autoRoot);
180 m_model.rootItem()->appendChild(manualRoot);
181
182 m_toolChainView = new QTreeView(this);
183 m_toolChainView->setUniformRowHeights(true);
184 m_toolChainView->setSelectionMode(QAbstractItemView::SingleSelection);
185 m_toolChainView->setSelectionBehavior(QAbstractItemView::SelectRows);
186 m_toolChainView->setModel(&m_model);
187 m_toolChainView->header()->setStretchLastSection(false);
188 m_toolChainView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
189 m_toolChainView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
190 m_toolChainView->expandAll();
191
192 m_addButton = new QPushButton(ToolChainOptionsPage::tr("Add"), this);
193 auto addMenu = new QMenu;
194 foreach (ToolChainFactory *factory, m_factories) {
195 QList<Utils::Id> languages = factory->supportedLanguages();
196 if (languages.isEmpty())
197 continue;
198
199 if (languages.count() == 1) {
200 addMenu->addAction(createAction(factory->displayName(), factory, languages.at(0)));
201 } else {
202 Utils::sort(languages, [](const Utils::Id &l1, const Utils::Id &l2) {
203 return ToolChainManager::displayNameOfLanguageId(l1) < ToolChainManager::displayNameOfLanguageId(l2);
204 });
205 auto subMenu = addMenu->addMenu(factory->displayName());
206 foreach (const Utils::Id &l, languages)
207 subMenu->addAction(createAction(ToolChainManager::displayNameOfLanguageId(l), factory, l));
208 }
209 }
210 m_addButton->setMenu(addMenu);
211
212 m_cloneButton = new QPushButton(ToolChainOptionsPage::tr("Clone"), this);
213 connect(m_cloneButton, &QAbstractButton::clicked, [this] { cloneToolChain(); });
214
215 m_delButton = new QPushButton(ToolChainOptionsPage::tr("Remove"), this);
216
217 m_removeAllButton = new QPushButton(ToolChainOptionsPage::tr("Remove All"), this);
218 connect(m_removeAllButton, &QAbstractButton::clicked, this,
219 [this] {
220 QList<ToolChainTreeItem *> itemsToRemove;
221 m_model.forAllItems([&itemsToRemove](TreeItem *item) {
222 if (item->level() != 3)
223 return;
224 const auto tcItem = static_cast<ToolChainTreeItem *>(item);
225 if (tcItem->toolChain->detection() != ToolChain::AutoDetectionFromSdk)
226 itemsToRemove << tcItem;
227 });
228 for (ToolChainTreeItem * const tcItem : qAsConst(itemsToRemove))
229 markForRemoval(tcItem);
230 });
231
232 m_redetectButton = new QPushButton(ToolChainOptionsPage::tr("Re-detect"), this);
233 connect(m_redetectButton, &QAbstractButton::clicked,
234 this, &ToolChainOptionsWidget::redetectToolchains);
235
236 m_detectionSettingsButton = new QPushButton(
237 ToolChainOptionsPage::tr("Auto-detection Settings..."), this);
238 connect(m_detectionSettingsButton, &QAbstractButton::clicked, this,
239 [this] {
240 DetectionSettingsDialog dlg(m_detectionSettings, this);
241 if (dlg.exec() == QDialog::Accepted)
242 m_detectionSettings = dlg.settings();
243 });
244
245 m_container = new DetailsWidget(this);
246 m_container->setState(DetailsWidget::NoSummary);
247 m_container->setVisible(false);
248
249 m_widgetStack = new QStackedWidget;
250 m_container->setWidget(m_widgetStack);
251
252 foreach (ToolChain *tc, ToolChainManager::toolChains())
253 insertToolChain(tc);
254
255 auto buttonLayout = new QVBoxLayout;
256 buttonLayout->setSpacing(6);
257 buttonLayout->setContentsMargins(0, 0, 0, 0);
258 buttonLayout->addWidget(m_addButton);
259 buttonLayout->addWidget(m_cloneButton);
260 buttonLayout->addWidget(m_delButton);
261 buttonLayout->addWidget(m_removeAllButton);
262 buttonLayout->addWidget(m_redetectButton);
263 buttonLayout->addWidget(m_detectionSettingsButton);
264 buttonLayout->addItem(new QSpacerItem(10, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
265
266 auto verticalLayout = new QVBoxLayout;
267 verticalLayout->addWidget(m_toolChainView);
268 verticalLayout->addWidget(m_container);
269
270 auto horizontalLayout = new QHBoxLayout(this);
271 horizontalLayout->addLayout(verticalLayout);
272 horizontalLayout->addLayout(buttonLayout);
273
274 connect(ToolChainManager::instance(), &ToolChainManager::toolChainAdded,
275 this, &ToolChainOptionsWidget::addToolChain);
276 connect(ToolChainManager::instance(), &ToolChainManager::toolChainRemoved,
277 this, &ToolChainOptionsWidget::removeToolChain);
278
279 connect(m_toolChainView->selectionModel(), &QItemSelectionModel::currentChanged,
280 this, &ToolChainOptionsWidget::toolChainSelectionChanged);
281 connect(ToolChainManager::instance(), &ToolChainManager::toolChainsChanged,
282 this, &ToolChainOptionsWidget::toolChainSelectionChanged);
283
284 connect(m_delButton, &QAbstractButton::clicked, [this] {
285 if (ToolChainTreeItem *item = currentTreeItem())
286 markForRemoval(item);
287 });
288
289 updateState();
290 }
291
292 void toolChainSelectionChanged();
293 void updateState();
294 void createToolChain(ToolChainFactory *factory, const Utils::Id &language);
295 void cloneToolChain();
296 ToolChainTreeItem *currentTreeItem();
297
298 void markForRemoval(ToolChainTreeItem *item);
299 ToolChainTreeItem *insertToolChain(ProjectExplorer::ToolChain *tc, bool changed = false); // Insert directly into model
300 void addToolChain(ProjectExplorer::ToolChain *);
301 void removeToolChain(ProjectExplorer::ToolChain *);
302
303 StaticTreeItem *parentForToolChain(ToolChain *tc);
createAction(const QString & name,ToolChainFactory * factory,Utils::Id language)304 QAction *createAction(const QString &name, ToolChainFactory *factory, Utils::Id language)
305 {
306 auto action = new QAction(name, nullptr);
307 connect(action, &QAction::triggered, [this, factory, language] { createToolChain(factory, language); });
308 return action;
309 }
310
311 void redetectToolchains();
312
313 void apply() final;
314
315 private:
316 TreeModel<TreeItem, ToolChainTreeItem> m_model;
317 QList<ToolChainFactory *> m_factories;
318 QTreeView *m_toolChainView;
319 DetailsWidget *m_container;
320 QStackedWidget *m_widgetStack;
321 QPushButton *m_addButton;
322 QPushButton *m_cloneButton;
323 QPushButton *m_delButton;
324 QPushButton *m_removeAllButton;
325 QPushButton *m_redetectButton;
326 QPushButton *m_detectionSettingsButton;
327
328 QHash<Utils::Id, QPair<StaticTreeItem *, StaticTreeItem *>> m_languageMap;
329
330 QList<ToolChainTreeItem *> m_toAddList;
331 QList<ToolChainTreeItem *> m_toRemoveList;
332
333 ToolchainDetectionSettings m_detectionSettings;
334 };
335
markForRemoval(ToolChainTreeItem * item)336 void ToolChainOptionsWidget::markForRemoval(ToolChainTreeItem *item)
337 {
338 m_model.takeItem(item);
339 if (m_toAddList.contains(item)) {
340 delete item->toolChain;
341 item->toolChain = nullptr;
342 m_toAddList.removeOne(item);
343 delete item;
344 } else {
345 m_toRemoveList.append(item);
346 }
347 }
348
insertToolChain(ToolChain * tc,bool changed)349 ToolChainTreeItem *ToolChainOptionsWidget::insertToolChain(ToolChain *tc, bool changed)
350 {
351 StaticTreeItem *parent = parentForToolChain(tc);
352 auto item = new ToolChainTreeItem(m_widgetStack, tc, changed);
353 parent->appendChild(item);
354
355 return item;
356 }
357
addToolChain(ToolChain * tc)358 void ToolChainOptionsWidget::addToolChain(ToolChain *tc)
359 {
360 foreach (ToolChainTreeItem *n, m_toAddList) {
361 if (n->toolChain == tc) {
362 // do not delete n: Still used elsewhere!
363 m_toAddList.removeOne(n);
364 return;
365 }
366 }
367
368 insertToolChain(tc);
369
370 updateState();
371 }
372
removeToolChain(ToolChain * tc)373 void ToolChainOptionsWidget::removeToolChain(ToolChain *tc)
374 {
375 foreach (ToolChainTreeItem *n, m_toRemoveList) {
376 if (n->toolChain == tc) {
377 m_toRemoveList.removeOne(n);
378 delete n;
379 return;
380 }
381 }
382
383 StaticTreeItem *parent = parentForToolChain(tc);
384 auto item = parent->findChildAtLevel(1, [tc](TreeItem *item) {
385 return static_cast<ToolChainTreeItem *>(item)->toolChain == tc;
386 });
387 m_model.destroyItem(item);
388
389 updateState();
390 }
391
parentForToolChain(ToolChain * tc)392 StaticTreeItem *ToolChainOptionsWidget::parentForToolChain(ToolChain *tc)
393 {
394 QPair<StaticTreeItem *, StaticTreeItem *> nodes = m_languageMap.value(tc->language());
395 return tc->isAutoDetected() ? nodes.first : nodes.second;
396 }
397
redetectToolchains()398 void ToolChainOptionsWidget::redetectToolchains()
399 {
400 QList<ToolChainTreeItem *> itemsToRemove;
401 QList<ToolChain *> knownTcs;
402 m_model.forAllItems([&itemsToRemove, &knownTcs](TreeItem *item) {
403 if (item->level() != 3)
404 return;
405 const auto tcItem = static_cast<ToolChainTreeItem *>(item);
406 if (tcItem->toolChain->isAutoDetected()
407 && tcItem->toolChain->detection() != ToolChain::AutoDetectionFromSdk) {
408 itemsToRemove << tcItem;
409 } else {
410 knownTcs << tcItem->toolChain;
411 }
412 });
413 QList<ToolChain *> toAdd;
414 QSet<ToolChain *> toDelete;
415 for (ToolChainFactory *f : ToolChainFactory::allToolChainFactories()) {
416 for (ToolChain * const tc : f->autoDetect(knownTcs, {})) { // FIXME: Pass device.
417 if (knownTcs.contains(tc) || toDelete.contains(tc))
418 continue;
419 const auto matchItem = [tc](const ToolChainTreeItem *item) {
420 return item->toolChain->compilerCommand() == tc->compilerCommand()
421 && item->toolChain->typeId() == tc->typeId()
422 && item->toolChain->language() == tc->language()
423 && item->toolChain->targetAbi() == tc->targetAbi();
424 };
425 ToolChainTreeItem * const item = findOrDefault(itemsToRemove, matchItem);
426 if (item) {
427 itemsToRemove.removeOne(item);
428 toDelete << tc;
429 continue;
430 }
431 knownTcs << tc;
432 toAdd << tc;
433 }
434 }
435 for (ToolChainTreeItem * const tcItem : qAsConst(itemsToRemove))
436 markForRemoval(tcItem);
437 for (ToolChain * const newTc : qAsConst(toAdd))
438 m_toAddList.append(insertToolChain(newTc, true));
439 qDeleteAll(toDelete);
440 }
441
toolChainSelectionChanged()442 void ToolChainOptionsWidget::toolChainSelectionChanged()
443 {
444 ToolChainTreeItem *item = currentTreeItem();
445
446 QWidget *currentTcWidget = item ? item->widget : nullptr;
447 if (currentTcWidget)
448 m_widgetStack->setCurrentWidget(currentTcWidget);
449 m_container->setVisible(currentTcWidget);
450 updateState();
451 }
452
apply()453 void ToolChainOptionsWidget::apply()
454 {
455 // Remove unused tool chains:
456 QList<ToolChainTreeItem *> nodes = m_toRemoveList;
457 foreach (ToolChainTreeItem *n, nodes)
458 ToolChainManager::deregisterToolChain(n->toolChain);
459
460 Q_ASSERT(m_toRemoveList.isEmpty());
461
462 // Update tool chains:
463 foreach (const Utils::Id &l, m_languageMap.keys()) {
464 const QPair<StaticTreeItem *, StaticTreeItem *> autoAndManual = m_languageMap.value(l);
465 for (StaticTreeItem *parent : {autoAndManual.first, autoAndManual.second}) {
466 for (TreeItem *item : *parent) {
467 auto tcItem = static_cast<ToolChainTreeItem *>(item);
468 Q_ASSERT(tcItem->toolChain);
469 if (!tcItem->toolChain->isAutoDetected() && tcItem->widget)
470 tcItem->widget->apply();
471 tcItem->changed = false;
472 tcItem->update();
473 }
474 }
475 }
476
477 // Add new (and already updated) tool chains
478 QStringList removedTcs;
479 nodes = m_toAddList;
480 foreach (ToolChainTreeItem *n, nodes) {
481 if (!ToolChainManager::registerToolChain(n->toolChain))
482 removedTcs << n->toolChain->displayName();
483 }
484 //
485 foreach (ToolChainTreeItem *n, m_toAddList)
486 markForRemoval(n);
487
488 qDeleteAll(m_toAddList);
489
490 if (removedTcs.count() == 1) {
491 QMessageBox::warning(Core::ICore::dialogParent(),
492 ToolChainOptionsPage::tr("Duplicate Compilers Detected"),
493 ToolChainOptionsPage::tr("The following compiler was already configured:<br>"
494 " %1<br>"
495 "It was not configured again.")
496 .arg(removedTcs.at(0)));
497
498 } else if (!removedTcs.isEmpty()) {
499 QMessageBox::warning(Core::ICore::dialogParent(),
500 ToolChainOptionsPage::tr("Duplicate Compilers Detected"),
501 ToolChainOptionsPage::tr("The following compilers were already configured:<br>"
502 " %1<br>"
503 "They were not configured again.")
504 .arg(removedTcs.join(QLatin1String(",<br> "))));
505 }
506 ToolChainManager::setDetectionSettings(m_detectionSettings);
507 }
508
createToolChain(ToolChainFactory * factory,const Utils::Id & language)509 void ToolChainOptionsWidget::createToolChain(ToolChainFactory *factory, const Utils::Id &language)
510 {
511 QTC_ASSERT(factory, return);
512 QTC_ASSERT(factory->canCreate(), return);
513 QTC_ASSERT(language.isValid(), return);
514
515 ToolChain *tc = factory->create();
516 if (!tc)
517 return;
518
519 tc->setDetection(ToolChain::ManualDetection);
520 tc->setLanguage(language);
521
522 auto item = insertToolChain(tc, true);
523 m_toAddList.append(item);
524
525 m_toolChainView->setCurrentIndex(m_model.indexForItem(item));
526 }
527
cloneToolChain()528 void ToolChainOptionsWidget::cloneToolChain()
529 {
530 ToolChainTreeItem *current = currentTreeItem();
531 if (!current)
532 return;
533
534 ToolChain *tc = current->toolChain->clone();
535 if (!tc)
536 return;
537
538 tc->setDetection(ToolChain::ManualDetection);
539 tc->setDisplayName(QCoreApplication::translate("ProjectExplorer::ToolChain", "Clone of %1")
540 .arg(current->toolChain->displayName()));
541
542 auto item = insertToolChain(tc, true);
543 m_toAddList.append(item);
544
545 m_toolChainView->setCurrentIndex(m_model.indexForItem(item));
546 }
547
updateState()548 void ToolChainOptionsWidget::updateState()
549 {
550 bool canCopy = false;
551 bool canDelete = false;
552 if (ToolChainTreeItem *item = currentTreeItem()) {
553 ToolChain *tc = item->toolChain;
554 canCopy = tc->isValid();
555 canDelete = tc->detection() != ToolChain::AutoDetectionFromSdk;
556 }
557
558 m_cloneButton->setEnabled(canCopy);
559 m_delButton->setEnabled(canDelete);
560 }
561
currentTreeItem()562 ToolChainTreeItem *ToolChainOptionsWidget::currentTreeItem()
563 {
564 QModelIndex index = m_toolChainView->currentIndex();
565 TreeItem *item = m_model.itemForIndex(index);
566 return (item && item->level() == 3) ? static_cast<ToolChainTreeItem *>(item) : nullptr;
567 }
568
569 // --------------------------------------------------------------------------
570 // ToolChainOptionsPage
571 // --------------------------------------------------------------------------
572
ToolChainOptionsPage()573 ToolChainOptionsPage::ToolChainOptionsPage()
574 {
575 setId(Constants::TOOLCHAIN_SETTINGS_PAGE_ID);
576 setDisplayName(ToolChainOptionsPage::tr("Compilers"));
577 setCategory(Constants::KITS_SETTINGS_CATEGORY);
578 setWidgetCreator([] { return new ToolChainOptionsWidget; });
579 }
580
581 } // namespace Internal
582 } // namespace ProjectExplorer
583