diff --git a/src/plugins/projectexplorer/kit.cpp b/src/plugins/projectexplorer/kit.cpp index fd374cc23b5..d2a83381254 100644 --- a/src/plugins/projectexplorer/kit.cpp +++ b/src/plugins/projectexplorer/kit.cpp @@ -768,11 +768,6 @@ void Kit::kitUpdated() static Id replacementKey() { return "IsReplacementKit"; } -void ProjectExplorer::Kit::makeReplacementKit() -{ - setValueSilently(replacementKey(), true); -} - bool Kit::isReplacementKit() const { return value(replacementKey()).toBool(); diff --git a/src/plugins/projectexplorer/kit.h b/src/plugins/projectexplorer/kit.h index 3a8ccdfee8d..16a5c1e151c 100644 --- a/src/plugins/projectexplorer/kit.h +++ b/src/plugins/projectexplorer/kit.h @@ -116,7 +116,6 @@ public: void setMutable(Utils::Id id, bool b); bool isMutable(Utils::Id id) const; - void makeReplacementKit(); bool isReplacementKit() const; void setRelevantAspects(const QSet &relevant); diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index da912177f68..c3019e2e203 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -195,6 +195,8 @@ public: mutable QVector m_sortedNodeList; Store m_extraData; + + QList m_vanishedTargets; }; ProjectPrivate::~ProjectPrivate() @@ -442,6 +444,54 @@ void Project::setActiveTarget(Target *target, SetActive cascade) } } +QList Project::vanishedTargets() const +{ + return d->m_vanishedTargets; +} + +void Project::removeVanishedTarget(int index) +{ + QTC_ASSERT(index >= 0 && index < d->m_vanishedTargets.size(), return); + d->m_vanishedTargets.removeAt(index); + emit vanishedTargetsChanged(); +} + +void Project::removeAllVanishedTargets() +{ + d->m_vanishedTargets.clear(); + emit vanishedTargetsChanged(); +} + +Target *Project::createKitAndTargetFromStore(const Utils::Store &store) +{ + const Id id = idFromMap(store); + Id deviceTypeId = Id::fromSetting(store.value(Target::deviceTypeKey())); + if (!deviceTypeId.isValid()) + deviceTypeId = Constants::DESKTOP_DEVICE_TYPE; + const QString formerKitName = store.value(Target::displayNameKey()).toString(); + Kit *k = KitManager::registerKit( + [deviceTypeId, &formerKitName](Kit *kit) { + const QString kitName = makeUniquelyNumbered(formerKitName, + transform(KitManager::kits(), + &Kit::unexpandedDisplayName)); + kit->setUnexpandedDisplayName(kitName); + DeviceTypeKitAspect::setDeviceTypeId(kit, deviceTypeId); + kit->setup(); + }, + id); + QTC_ASSERT(k, return nullptr); + auto t = std::make_unique(this, k, Target::_constructor_tag{}); + if (!t->fromMap(store)) + return nullptr; + + if (t->runConfigurations().isEmpty() && t->buildConfigurations().isEmpty()) + return nullptr; + + auto pointer = t.get(); + addTarget(std::move(t)); + return pointer; +} + Tasks Project::projectIssues(const Kit *k) const { Tasks result; @@ -566,6 +616,23 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget) return !fatalError; } +bool Project::copySteps(const Utils::Store &store, Kit *targetKit) +{ + Target *t = target(targetKit->id()); + if (!t) { + auto t = std::make_unique(this, targetKit, Target::_constructor_tag{}); + if (!t->fromMap(store)) + return false; + + if (t->runConfigurations().isEmpty() && t->buildConfigurations().isEmpty()) + return false; + + addTarget(std::move(t)); + return true; + } + return t->addConfigurationsFromMap(store, /*setActiveConfigurations=*/false); +} + bool Project::setupTarget(Target *t) { if (d->m_needsBuildConfigurations) @@ -700,11 +767,19 @@ FilePaths Project::files(const NodeMatcher &filter) const void Project::toMap(Store &map) const { const QList ts = targets(); + const QList vts = vanishedTargets(); map.insert(ACTIVE_TARGET_KEY, ts.indexOf(d->m_activeTarget)); - map.insert(TARGET_COUNT_KEY, ts.size()); - for (int i = 0; i < ts.size(); ++i) - map.insert(numberedKey(TARGET_KEY_PREFIX, i), variantFromStore(ts.at(i)->toMap())); + map.insert(TARGET_COUNT_KEY, ts.size() + vts.size()); + int index = 0; + for (Target *t : ts) { + map.insert(numberedKey(TARGET_KEY_PREFIX, index), variantFromStore(t->toMap())); + ++index; + } + for (const Store &store : vts) { + map.insert(numberedKey(TARGET_KEY_PREFIX, index), variantFromStore(store)); + ++index; + } map.insert(EDITOR_SETTINGS_KEY, variantFromStore(d->m_editorConfiguration.toMap())); if (!d->m_pluginSettings.isEmpty()) @@ -826,32 +901,16 @@ void Project::createTargetFromMap(const Store &map, int index) if (ICore::isQtDesignStudio()) return; - Id deviceTypeId = Id::fromSetting(targetMap.value(Target::deviceTypeKey())); - if (!deviceTypeId.isValid()) - deviceTypeId = Constants::DESKTOP_DEVICE_TYPE; + d->m_vanishedTargets.append(targetMap); const QString formerKitName = targetMap.value(Target::displayNameKey()).toString(); - k = KitManager::registerKit( - [deviceTypeId, &formerKitName](Kit *kit) { - const QString kitNameSuggestion - = formerKitName.contains(::PE::Tr::tr("Replacement for")) - ? formerKitName - : ::PE::Tr::tr("Replacement for \"%1\"").arg(formerKitName); - const QString tempKitName = makeUniquelyNumbered(kitNameSuggestion, - transform(KitManager::kits(), &Kit::unexpandedDisplayName)); - kit->setUnexpandedDisplayName(tempKitName); - DeviceTypeKitAspect::setDeviceTypeId(kit, deviceTypeId); - kit->makeReplacementKit(); - kit->setup(); - }, - id); - QTC_ASSERT(k, return); TaskHub::addTask(BuildSystemTask( Task::Warning, ::PE::Tr::tr( "Project \"%1\" was configured for " - "kit \"%2\" with id %3, which does not exist anymore. The new kit \"%4\" was " - "created in its place, in an attempt not to lose custom project settings.") - .arg(displayName(), formerKitName, id.toString(), k->displayName()))); + "kit \"%2\" with id %3, which does not exist anymore. You can create a new kit " + "or copy the steps of the vanished kit to another kit in %4 mode.") + .arg(displayName(), formerKitName, id.toString(), Tr::tr("Projects")))); + return; } auto t = std::make_unique(this, k, Target::_constructor_tag{}); diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index 2304dd3850c..06508a18fe1 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -97,6 +97,7 @@ public: virtual Tasks projectIssues(const Kit *k) const; static bool copySteps(Target *sourceTarget, Target *newTarget); + bool copySteps(const Utils::Store &store, Kit *targetKit); void saveSettings(); enum class RestoreResult { Ok, Error, UserAbort }; @@ -179,6 +180,11 @@ public: Utils::MacroExpander *expander, const std::function &projectGetter); + QList vanishedTargets() const; + void removeVanishedTarget(int index); + void removeAllVanishedTargets(); + Target *createKitAndTargetFromStore(const Utils::Store &store); + signals: void projectFileIsDirty(const Utils::FilePath &path); @@ -193,6 +199,8 @@ signals: void removedTarget(ProjectExplorer::Target *target); void addedTarget(ProjectExplorer::Target *target); + void vanishedTargetsChanged(); + void settingsLoaded(); void aboutToSaveSettings(); diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp index 40d4bdd8386..88b98ee6aa9 100644 --- a/src/plugins/projectexplorer/projectwindow.cpp +++ b/src/plugins/projectexplorer/projectwindow.cpp @@ -4,6 +4,8 @@ #include "projectwindow.h" #include "buildinfo.h" +#include "devicesupport/idevicefactory.h" + #include "kit.h" #include "kitmanager.h" #include "kitoptionspage.h" @@ -204,6 +206,154 @@ void BuildSystemOutputWindow::updateFilter() m_invertFilterAction.isChecked()); } +class VanishedTargetPanelItem : public TreeItem +{ +public: + VanishedTargetPanelItem(const Store &store, Project *project) + : m_store(store) + , m_project(project) + {} + + QVariant data(int column, int role) const override; + bool setData(int column, const QVariant &data, int role) override; + Qt::ItemFlags flags(int column) const override; + +protected: + Store m_store; + QPointer m_project; +}; + +static QString deviceTypeDisplayName(const Store &store) +{ + Id deviceTypeId = Id::fromSetting(store.value(Target::deviceTypeKey())); + if (!deviceTypeId.isValid()) + deviceTypeId = Constants::DESKTOP_DEVICE_TYPE; + + QString typeDisplayName = Tr::tr("Unknown device type"); + if (deviceTypeId.isValid()) { + if (IDeviceFactory *factory = IDeviceFactory::find(deviceTypeId)) + typeDisplayName = factory->displayName(); + } + return typeDisplayName; +} +static QString msgOptionsForRestoringSettings() +{ + return "" + + Tr::tr("The project was configured for kits that no longer exist. Select one of the " + "following options in the context menu to restore the project's settings:") + + "
  • " + + Tr::tr("Create a new kit with the same name for the same device type, with the " + "original build, deploy, and run steps. Other kit settings are not restored.") + + "
  • " + Tr::tr("Copy the build, deploy, and run steps to another kit.") + + "
"; +} + +QVariant VanishedTargetPanelItem::data(int column, int role) const +{ + Q_UNUSED(column) + switch (role) { + case Qt::DisplayRole: + //: vanished target display role: vanished target name (device type name) + return Tr::tr("%1 (%2)").arg(m_store.value(Target::displayNameKey()).toString(), + deviceTypeDisplayName(m_store)); + case Qt::ToolTipRole: + return msgOptionsForRestoringSettings(); + } + + return {}; +} + +bool VanishedTargetPanelItem::setData(int column, const QVariant &data, int role) +{ + Q_UNUSED(column) + + const auto addToMenu = [this](QMenu *menu) { + const int index = indexInParent(); + menu->addAction(Tr::tr("Create a New Kit"), + m_project.data(), + [index, store = m_store, project = m_project] { + Target *t = project->createKitAndTargetFromStore(store); + if (t) { + project->setActiveTarget(t, SetActive::Cascade); + project->removeVanishedTarget(index); + } + }); + QMenu *copyMenu = menu->addMenu(Tr::tr("Copy Steps to Another Kit")); + const QList kits = KitManager::kits(); + for (Kit *kit : kits) { + QAction *copyAction = copyMenu->addAction(kit->displayName()); + QObject::connect(copyAction, + &QAction::triggered, + [index, store = m_store, project = m_project, kit] { + if (project->copySteps(store, kit)) + project->removeVanishedTarget(index); + }); + } + menu->addSeparator(); + menu->addAction(Tr::tr("Remove Vanished Target \"%1\"") + .arg(m_store.value(Target::displayNameKey()).toString()), + m_project.data(), + [index, project = m_project] { project->removeVanishedTarget(index); }); + menu->addAction(Tr::tr("Remove All Vanished Targets"), + m_project.data(), + [project = m_project] { project->removeAllVanishedTargets(); }); + }; + + if (role == ContextMenuItemAdderRole) { + auto *menu = data.value(); + addToMenu(menu); + return true; + } + if (role == ItemActivatedDirectlyRole) { + QMenu menu; + addToMenu(&menu); + menu.exec(QCursor::pos()); + } + return false; +} + +Qt::ItemFlags VanishedTargetPanelItem::flags(int column) const +{ + Q_UNUSED(column) + return Qt::ItemIsEnabled; +} + +// The middle part of the second tree level, i.e. the list of vanished configured kits/targets. +class VanishedTargetsGroupItem : public TreeItem +{ +public: + explicit VanishedTargetsGroupItem(Project *project) + : m_project(project) + { + QTC_ASSERT(m_project, return); + rebuild(); + } + + void rebuild() + { + removeChildren(); + for (const Store &store : m_project->vanishedTargets()) + appendChild(new VanishedTargetPanelItem(store, m_project)); + } + + Qt::ItemFlags flags(int) const override { return Qt::NoItemFlags; } + + QVariant data(int column, int role) const override + { + Q_UNUSED(column) + switch (role) { + case Qt::DisplayRole: + return Tr::tr("Vanished Targets"); + case Qt::ToolTipRole: + return msgOptionsForRestoringSettings(); + } + return {}; + } + +private: + QPointer m_project; +}; + // Standard third level for the generic case: i.e. all except for the Build/Run page class MiscSettingsPanelItem : public TreeItem // TypedTreeItem { @@ -338,9 +488,26 @@ public: : m_project(project), m_changeListener(changeListener) { QTC_ASSERT(m_project, return); - QString display = Tr::tr("Build & Run"); - appendChild(m_targetsItem = new TargetGroupItem(display, project)); - appendChild(m_miscItem = new MiscSettingsGroupItem(project)); + appendChild(m_targetsItem = new TargetGroupItem(Tr::tr("Build & Run"), m_project)); + if (!m_project->vanishedTargets().isEmpty()) + appendChild(m_vanishedTargetsItem = new VanishedTargetsGroupItem(m_project)); + appendChild(m_miscItem = new MiscSettingsGroupItem(m_project)); + QObject::connect( + m_project, + &Project::vanishedTargetsChanged, + &m_guard, + [this] { rebuildVanishedTargets(); }, + Qt::QueuedConnection /* this is triggered by a child item, so queue */); + } + + void rebuildVanishedTargets() + { + if (m_vanishedTargetsItem) { + if (m_project->vanishedTargets().isEmpty()) + removeChildAt(indexOf(m_vanishedTargetsItem)); + else + m_vanishedTargetsItem->rebuild(); + } } QVariant data(int column, int role) const override @@ -423,9 +590,11 @@ public: } private: + QObject m_guard; int m_currentChildIndex = 0; // Start with Build & Run. Project *m_project = nullptr; TargetGroupItem *m_targetsItem = nullptr; + VanishedTargetsGroupItem *m_vanishedTargetsItem; MiscSettingsGroupItem *m_miscItem = nullptr; const std::function m_changeListener; }; diff --git a/src/plugins/projectexplorer/target.cpp b/src/plugins/projectexplorer/target.cpp index 3c5e87effe6..8231366dfce 100644 --- a/src/plugins/projectexplorer/target.cpp +++ b/src/plugins/projectexplorer/target.cpp @@ -889,15 +889,26 @@ bool Target::fromMap(const Store &map) { QTC_ASSERT(d->m_kit == KitManager::kit(id()), return false); + if (!addConfigurationsFromMap(map, /*setActiveConfigurations=*/true)) + return false; + + if (map.contains(PLUGIN_SETTINGS_KEY)) + d->m_pluginSettings = storeFromVariant(map.value(PLUGIN_SETTINGS_KEY)); + + return true; +} + +bool Target::addConfigurationsFromMap(const Utils::Store &map, bool setActiveConfigurations) +{ bool ok; int bcCount = map.value(BC_COUNT_KEY, 0).toInt(&ok); if (!ok || bcCount < 0) bcCount = 0; int activeConfiguration = map.value(ACTIVE_BC_KEY, 0).toInt(&ok); - if (!ok || activeConfiguration < 0) - activeConfiguration = 0; - if (0 > activeConfiguration || bcCount < activeConfiguration) + if (!ok || 0 > activeConfiguration || bcCount < activeConfiguration) activeConfiguration = 0; + if (!setActiveConfigurations) + activeConfiguration = -1; for (int i = 0; i < bcCount; ++i) { const Key key = numberedKey(BC_KEY_PREFIX, i); @@ -921,10 +932,10 @@ bool Target::fromMap(const Store &map) if (!ok || dcCount < 0) dcCount = 0; activeConfiguration = map.value(ACTIVE_DC_KEY, 0).toInt(&ok); - if (!ok || activeConfiguration < 0) - activeConfiguration = 0; - if (0 > activeConfiguration || dcCount < activeConfiguration) + if (!ok || 0 > activeConfiguration || dcCount < activeConfiguration) activeConfiguration = 0; + if (!setActiveConfigurations) + activeConfiguration = -1; for (int i = 0; i < dcCount; ++i) { const Key key = numberedKey(DC_KEY_PREFIX, i); @@ -948,10 +959,10 @@ bool Target::fromMap(const Store &map) if (!ok || rcCount < 0) rcCount = 0; activeConfiguration = map.value(ACTIVE_RC_KEY, 0).toInt(&ok); - if (!ok || activeConfiguration < 0) - activeConfiguration = 0; - if (0 > activeConfiguration || rcCount < activeConfiguration) + if (!ok || 0 > activeConfiguration || rcCount < activeConfiguration) activeConfiguration = 0; + if (!setActiveConfigurations) + activeConfiguration = -1; for (int i = 0; i < rcCount; ++i) { const Key key = numberedKey(RC_KEY_PREFIX, i); @@ -972,9 +983,6 @@ bool Target::fromMap(const Store &map) setActiveRunConfiguration(rc); } - if (map.contains(PLUGIN_SETTINGS_KEY)) - d->m_pluginSettings = storeFromVariant(map.value(PLUGIN_SETTINGS_KEY)); - return true; } diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h index fa8fa4b3b0e..395ce6005cd 100644 --- a/src/plugins/projectexplorer/target.h +++ b/src/plugins/projectexplorer/target.h @@ -147,6 +147,7 @@ signals: private: bool fromMap(const Utils::Store &map); + bool addConfigurationsFromMap(const Utils::Store &map, bool setActiveConfigurations); void updateDeviceState();