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                                                       "&nbsp;%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                                                       "&nbsp;%1<br>"
503                                                       "They were not configured again.")
504                                                       .arg(removedTcs.join(QLatin1String(",<br>&nbsp;"))));
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