// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "targetsetuppage.h" #include "buildinfo.h" #include "devicesupport/devicekitaspects.h" #include "importwidget.h" #include "kit.h" #include "kitmanager.h" #include "project.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "projectimporter.h" #include "target.h" #include "targetsetupwidget.h" #include "task.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace ProjectExplorer { namespace Internal { static TasksGenerator defaultTasksGenerator(const TasksGenerator &childGenerator) { return [childGenerator](const Kit *k) -> Tasks { if (!k->isValid()) return {CompileTask(Task::Error, Tr::tr("Kit is not valid."))}; if (childGenerator) return childGenerator(k); return {}; }; } class TargetSetupPagePrivate { public: explicit TargetSetupPagePrivate(TargetSetupPage *parent); void doInitializePage(); void handleKitAddition(Kit *k); void handleKitRemoval(Kit *k); void handleKitUpdate(Kit *k); void updateVisibility(); void reLayout(); static bool compareKits(const Kit *k1, const Kit *k2); std::vector sortedWidgetList() const; void kitSelectionChanged(); bool isUpdating() const; void selectAtLeastOneEnabledKit(); void ensureSelectedKitIsVisible(); void removeWidget(Kit *k) { removeWidget(widget(k)); } void removeWidget(Internal::TargetSetupWidget *w); TargetSetupWidget *addWidget(Kit *k); void connectWidget(TargetSetupWidget *w); void toggleVisibility(TargetSetupWidget *w); void addAdditionalWidgets(); void removeAdditionalWidgets(); void updateWidget(Internal::TargetSetupWidget *widget); bool isUsable(const Kit *kit) const; void setupImports(); void import(const FilePath &path, bool silent = false); void setupWidgets(const QString &filterText = QString()); void reset(); TargetSetupWidget *widget(const Id kitId) const; TargetSetupWidget *widget(const Kit *k) const { return k ? widget(k->id()) : nullptr; } TargetSetupPage * const q; QObject *guard = new QObject{q}; QWidget *centralWidget; QScrollArea *scrollArea; QLabel *headerLabel; QLabel *noValidKitLabel; QCheckBox *allKitsCheckBox; FancyLineEdit *kitFilterLineEdit; QCheckBox *hideUnsuitableKitsCheckBox; TasksGenerator tasksGenerator; QPointer importer; FilePath projectPath; Project *project = nullptr; QString defaultShadowBuildLocation; std::vector widgets; Internal::ImportWidget *importWidget = nullptr; QSpacerItem *spacer; }; } // namespace Internal using namespace Internal; TargetSetupPage::TargetSetupPage(QWidget *parent) : WizardPage(parent) , d(new TargetSetupPagePrivate(this)) { setObjectName(QLatin1String("TargetSetupPage")); setWindowTitle(Tr::tr("Select Kits for Your Project")); setTitle(Tr::tr("Kit Selection")); QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); policy.setHorizontalStretch(0); policy.setVerticalStretch(0); setSizePolicy(policy); setProperty(SHORT_TITLE_PROPERTY, Tr::tr("Kits")); } void TargetSetupPage::initializePage() { if (KitManager::isLoaded()) { d->doInitializePage(); } else { connect(KitManager::instance(), &KitManager::kitsLoaded, d->guard, [this] { d->doInitializePage(); }); } } void TargetSetupPage::setTasksGenerator(const TasksGenerator &tasksGenerator) { d->tasksGenerator = defaultTasksGenerator(tasksGenerator); } QList TargetSetupPage::selectedKits() const { QList result; for (TargetSetupWidget *w : d->widgets) { if (w->isKitSelected()) result.append(w->kit()->id()); } return result; } TargetSetupPage::~TargetSetupPage() { delete d->guard; d->reset(); delete d->spacer; delete d; } bool TargetSetupPage::isComplete() const { return anyOf(d->widgets, [](const TargetSetupWidget *w) { return w->isKitSelected(); }); } void TargetSetupPagePrivate::setupWidgets(const QString &filterText) { for (Kit *k : KitManager::sortedKits()) { if (!filterText.isEmpty() && !k->displayName().contains(filterText, Qt::CaseInsensitive)) continue; if (importer && !importer->filter(k)) continue; const auto widget = new TargetSetupWidget(k, projectPath); updateWidget(widget); widgets.push_back(widget); centralWidget->layout()->addWidget(widget); } addAdditionalWidgets(); // Setup import widget: importWidget->setCurrentDirectory(projectPath.parentDir()); kitSelectionChanged(); updateVisibility(); for (TargetSetupWidget * const w : widgets) { connectWidget(w); toggleVisibility(w); } } void TargetSetupPagePrivate::reset() { removeAdditionalWidgets(); while (widgets.size() > 0) { TargetSetupWidget *w = widgets.back(); Kit *k = w->kit(); if (k && importer) importer->removeProject(k); removeWidget(w); } allKitsCheckBox->setChecked(false); hideUnsuitableKitsCheckBox->setChecked(true); } TargetSetupWidget *TargetSetupPagePrivate::widget(const Id kitId) const { return findOrDefault(widgets, [kitId](const TargetSetupWidget *w) { return w->kit() && w->kit()->id() == kitId; }); } void TargetSetupPage::setProjectAndPath(Project *project, const FilePath &path) { d->project = project; d->projectPath = path; if (!d->projectPath.isEmpty()) { QFileInfo fileInfo(QDir::cleanPath(path.toUrlishString())); QStringList subDirsList = fileInfo.absolutePath().split('/'); d->headerLabel->setText(Tr::tr("The following kits can be used for project %1:", "%1: Project name").arg(subDirsList.last())); } d->headerLabel->setVisible(!d->projectPath.isEmpty()); } void TargetSetupPage::setProjectImporter(ProjectImporter *importer) { if (importer == d->importer) return; d->importer = importer; d->importWidget->setVisible(d->importer); } bool TargetSetupPage::importLineEditHasFocus() const { return d->importWidget->ownsReturnKey(); } void TargetSetupPagePrivate::setupImports() { if (!importer || projectPath.isEmpty()) return; const FilePaths toImport = importer->importCandidates(); for (const FilePath &path : toImport) import(path, true); } void TargetSetupPagePrivate::handleKitAddition(Kit *k) { if (isUpdating()) return; QTC_ASSERT(!widget(k), return); addWidget(k); kitSelectionChanged(); updateVisibility(); } void TargetSetupPagePrivate::handleKitRemoval(Kit *k) { if (isUpdating()) return; if (importer) importer->cleanupKit(k); removeWidget(k); kitSelectionChanged(); updateVisibility(); } void TargetSetupPagePrivate::handleKitUpdate(Kit *k) { if (isUpdating()) return; if (importer) importer->makePersistent(k); const auto newWidgetList = sortedWidgetList(); if (newWidgetList != widgets) { // Sorting has changed. widgets = newWidgetList; reLayout(); } updateWidget(widget(k)); kitSelectionChanged(); updateVisibility(); } void TargetSetupPagePrivate::selectAtLeastOneEnabledKit() { if (anyOf(widgets, [](const TargetSetupWidget *w) { return w->isKitSelected(); })) { // Something is already selected, we are done. return; } TargetSetupWidget *toCheckWidget = nullptr; const Kit *defaultKit = KitManager::defaultKit(); auto isPreferred = [this](const TargetSetupWidget *w) { const Tasks tasks = tasksGenerator(w->kit()); return w->isValid() && tasks.isEmpty(); }; // Use default kit if that is preferred: toCheckWidget = findOrDefault(widgets, [defaultKit, isPreferred](const TargetSetupWidget *w) { return isPreferred(w) && w->kit() == defaultKit; }); if (!toCheckWidget) { // Use the first preferred widget: toCheckWidget = findOrDefault(widgets, isPreferred); } if (!toCheckWidget) { // Use default kit if it is enabled: toCheckWidget = findOrDefault(widgets, [defaultKit](const TargetSetupWidget *w) { return w->isValid() && w->kit() == defaultKit; }); } if (!toCheckWidget) { // Use the first enabled widget: toCheckWidget = findOrDefault(widgets, [](const TargetSetupWidget *w) { return w->isValid(); }); } if (toCheckWidget) { toCheckWidget->setKitSelected(true); emit q->completeChanged(); // FIXME: Is this necessary? } } void TargetSetupPagePrivate::ensureSelectedKitIsVisible() { if (TargetSetupWidget * const w = Utils::findOrDefault(widgets, &TargetSetupWidget::isKitSelected)) { const int xScrollPos = scrollArea->horizontalScrollBar() ? scrollArea->horizontalScrollBar()->value() : 0; scrollArea->ensureWidgetVisible(w); if (scrollArea->horizontalScrollBar()) scrollArea->horizontalScrollBar()->setValue(xScrollPos); } } void TargetSetupPagePrivate::updateVisibility() { const bool hasUsableKits = KitManager::kit([this](const Kit *k) { return isUsable(k); }); noValidKitLabel->setVisible(!hasUsableKits); allKitsCheckBox->setVisible(hasUsableKits); emit q->completeChanged(); } void TargetSetupPagePrivate::reLayout() { removeAdditionalWidgets(); for (TargetSetupWidget * const w : std::as_const(widgets)) centralWidget->layout()->removeWidget(w); for (TargetSetupWidget * const w : std::as_const(widgets)) centralWidget->layout()->addWidget(w); addAdditionalWidgets(); } bool TargetSetupPagePrivate::compareKits(const Kit *k1, const Kit *k2) { const QString name1 = k1->displayName(); const QString name2 = k2->displayName(); if (name1 < name2) return true; if (name1 > name2) return false; return k1 < k2; } std::vector TargetSetupPagePrivate::sortedWidgetList() const { return sorted(widgets, [](const TargetSetupWidget *w1, const TargetSetupWidget *w2) { return compareKits(w1->kit(), w2->kit()); }); } void TargetSetupPage::openOptions() { Core::ICore::showOptionsDialog(Constants::KITS_SETTINGS_PAGE_ID, this); } TargetSetupPagePrivate::TargetSetupPagePrivate(TargetSetupPage *parent) : q(parent) { tasksGenerator = defaultTasksGenerator({}); importWidget = new ImportWidget(q); importWidget->setVisible(false); spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); headerLabel = new QLabel(q); headerLabel->setWordWrap(true); headerLabel->setVisible(false); noValidKitLabel = new QLabel(q); noValidKitLabel->setWordWrap(true); noValidKitLabel->setText("" + Tr::tr("No suitable kits found.") + "
" + Tr::tr("Add a kit in the " "options or via the maintenance tool of" " the SDK.")); noValidKitLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); noValidKitLabel->setVisible(false); allKitsCheckBox = new QCheckBox(q); allKitsCheckBox->setTristate(true); allKitsCheckBox->setText(Tr::tr("Select all kits")); kitFilterLineEdit = new FancyLineEdit(q); kitFilterLineEdit->setFiltering(true); kitFilterLineEdit->setPlaceholderText(Tr::tr("Type to filter kits by name...")); hideUnsuitableKitsCheckBox = new QCheckBox(Tr::tr("Hide unsuitable kits"), q); centralWidget = new QWidget(q); centralWidget->setLayout(new QVBoxLayout); centralWidget->layout()->setContentsMargins(0, 0, 0, 0); scrollArea = new QScrollArea(q); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidgetResizable(true); scrollArea->setWidget(centralWidget); const auto optionsLayout = new QHBoxLayout; optionsLayout->addWidget(hideUnsuitableKitsCheckBox); optionsLayout->addWidget(allKitsCheckBox); optionsLayout->addSpacing(10); optionsLayout->addWidget(kitFilterLineEdit); const auto mainLayout = new QVBoxLayout(q); mainLayout->addWidget(headerLabel); mainLayout->addLayout(optionsLayout); mainLayout->addWidget(noValidKitLabel); mainLayout->addWidget(scrollArea, 255); QObject::connect(noValidKitLabel, &QLabel::linkActivated, guard, [this] { q->openOptions(); }); QObject::connect(allKitsCheckBox, &QAbstractButton::clicked, guard, [this] { q->changeAllKitsSelections(); }); const auto toggleTargetWidgetVisibility = [this] { for (TargetSetupWidget *widget : widgets) toggleVisibility(widget); }; QObject::connect(hideUnsuitableKitsCheckBox, &QCheckBox::toggled, guard, toggleTargetWidgetVisibility); QObject::connect(kitFilterLineEdit, &FancyLineEdit::filterChanged, guard, toggleTargetWidgetVisibility); KitManager *km = KitManager::instance(); // do note that those slots are triggered once *per* targetsetuppage // thus the same slot can be triggered multiple times on different instances! QObject::connect(km, &KitManager::kitAdded, guard, [this](Kit *k) { handleKitAddition(k); }); QObject::connect(km, &KitManager::kitRemoved, guard, [this](Kit *k) { handleKitRemoval(k); }); QObject::connect(km, &KitManager::kitUpdated, guard, [this](Kit *k) { handleKitUpdate(k); }); QObject::connect(importWidget, &ImportWidget::importFrom, guard, [this](const FilePath &dir) { import(dir); }); QObject::connect(KitManager::instance(), &KitManager::kitsChanged, guard, [this] { updateVisibility(); }); toggleTargetWidgetVisibility(); } void TargetSetupPagePrivate::kitSelectionChanged() { int selected = 0; int deselected = 0; for (const TargetSetupWidget *widget : widgets) { if (widget->isKitSelected()) ++selected; else ++deselected; } if (selected > 0 && deselected > 0) allKitsCheckBox->setCheckState(Qt::PartiallyChecked); else if (selected > 0 && deselected == 0) allKitsCheckBox->setCheckState(Qt::Checked); else allKitsCheckBox->setCheckState(Qt::Unchecked); } void TargetSetupPagePrivate::doInitializePage() { reset(); setupWidgets(); setupImports(); selectAtLeastOneEnabledKit(); updateVisibility(); } void TargetSetupPage::showEvent(QShowEvent *event) { WizardPage::showEvent(event); d->kitFilterLineEdit->setFocus(); // Ensure "Configure Project" gets triggered on d->ensureSelectedKitIsVisible(); } void TargetSetupPage::changeAllKitsSelections() { if (d->allKitsCheckBox->checkState() == Qt::PartiallyChecked) d->allKitsCheckBox->setCheckState(Qt::Checked); bool checked = d->allKitsCheckBox->isChecked(); for (TargetSetupWidget *widget : d->widgets) { if (!checked || widget->isValid()) widget->setKitSelected(checked); } emit completeChanged(); } bool TargetSetupPagePrivate::isUpdating() const { return importer && importer->isUpdating(); } void TargetSetupPagePrivate::import(const FilePath &path, bool silent) { if (!importer) return; QHash> buildInfos; for (const BuildInfo &info : importer->import(path, silent)) { TargetSetupWidget *w = widget(info.kitId); if (!w) { Kit *k = KitManager::kit(info.kitId); QTC_ASSERT(k, continue); w = addWidget(k); } QTC_ASSERT(w, continue); buildInfos[w] << info; } for (auto it = buildInfos.cbegin(); it != buildInfos.cend(); ++it) { it.key()->addBuildInfos(it.value(), true); it.key()->setKitSelected(true); it.key()->expandWidget(); kitSelectionChanged(); } emit q->completeChanged(); } void TargetSetupPagePrivate::removeWidget(TargetSetupWidget *w) { if (!w) return; w->deleteLater(); w->clearKit(); widgets.erase(std::find(widgets.begin(), widgets.end(), w)); } TargetSetupWidget *TargetSetupPagePrivate::addWidget(Kit *k) { const auto widget = new TargetSetupWidget(k, projectPath); updateWidget(widget); connectWidget(widget); toggleVisibility(widget); // Insert widget, sorted. const auto insertionPos = std::find_if(widgets.begin(), widgets.end(), [k](const TargetSetupWidget *w) { return compareKits(k, w->kit()); }); const bool addedToEnd = insertionPos == widgets.end(); widgets.insert(insertionPos, widget); if (addedToEnd) { removeAdditionalWidgets(); centralWidget->layout()->addWidget(widget); addAdditionalWidgets(); } else { reLayout(); } return widget; } void TargetSetupPagePrivate::connectWidget(TargetSetupWidget *w) { QObject::connect(w, &TargetSetupWidget::selectedToggled, guard, [this] { kitSelectionChanged(); }); QObject::connect(w, &TargetSetupWidget::selectedToggled, guard, [this] { emit q->completeChanged(); }); QObject::connect(w, &TargetSetupWidget::validToggled, guard, [w, this] { toggleVisibility(w); }); } void TargetSetupPagePrivate::toggleVisibility(TargetSetupWidget *w) { const bool shouldBeVisible = [w, this] { if (!w->isValid() && hideUnsuitableKitsCheckBox->isChecked()) return false; const QString filterText = kitFilterLineEdit->text(); return filterText.isEmpty() || w->kit()->displayName().contains(filterText, Qt::CaseInsensitive); }(); if (shouldBeVisible) { if (!w->isVisible()) // Prevent flickering. w->show(); } else { w->hide(); } } void TargetSetupPagePrivate::addAdditionalWidgets() { centralWidget->layout()->addWidget(importWidget); centralWidget->layout()->addItem(spacer); } void TargetSetupPagePrivate::removeAdditionalWidgets() { centralWidget->layout()->removeWidget(importWidget); centralWidget->layout()->removeItem(spacer); } void TargetSetupPagePrivate::updateWidget(TargetSetupWidget *widget) { QTC_ASSERT(widget, return ); widget->update(tasksGenerator); } bool TargetSetupPagePrivate::isUsable(const Kit *kit) const { return !containsType(tasksGenerator(kit), Task::Error); } void TargetSetupPage::setupProject(Project *project) { QTC_ASSERT(project, return); QList toSetUp; for (TargetSetupWidget *widget : d->widgets) { if (!widget->isKitSelected()) continue; Kit *k = widget->kit(); if (k && d->importer) d->importer->makePersistent(k); toSetUp << widget->selectedBuildInfoList(); widget->clearKit(); } project->setup(toSetUp); toSetUp.clear(); // Only reset now that toSetUp has been cleared! d->reset(); Target *activeTarget = nullptr; if (d->importer) activeTarget = d->importer->preferredTarget(project->targets()); if (activeTarget) project->setActiveTarget(activeTarget, SetActive::NoCascade); } } // namespace ProjectExplorer