ProjectExplorer: Do not create replacement kits automatically

Instead keep a list of "vanished targets".

These are shown in Projects mode in a separate list under the list of
kits. Via the context menus of those items (or with single-click), the
user can choose to either copy the steps of the vanished target to
another kit (already configured or not), or to create a new replacement
kit for it (similar to the previous replacement kits, with the same
device type and the steps restored, but no other parameters from the
kit restored - the project doesn't save that information), or remove
them.

The vanished targets are not removed from the project's settings as
long as the user doesn't create a kit or copy the steps, so if the kit
re-appears on a later run of Qt Creator, the original target is
restored for that kit and the entry in the "vanished targets" list
automatically vanishes.

This has the advantage that in contrast to the replacement kits, the
vanished targets are clearly separate from "normal" kits, and that they
are local to the project. Nothing is left behind after closing the
project.

Change-Id: Iccec04fea38cd55ff683665c9cf4edc9a2388c82
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Eike Ziller
2023-11-22 14:18:32 +01:00
parent 052ea6d231
commit 38da356153
7 changed files with 284 additions and 45 deletions

View File

@@ -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();

View File

@@ -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<Utils::Id> &relevant);

View File

@@ -195,6 +195,8 @@ public:
mutable QVector<const Node *> m_sortedNodeList;
Store m_extraData;
QList<Store> m_vanishedTargets;
};
ProjectPrivate::~ProjectPrivate()
@@ -442,6 +444,54 @@ void Project::setActiveTarget(Target *target, SetActive cascade)
}
}
QList<Store> 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<Target>(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<Target>(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<Target *> ts = targets();
const QList<Store> 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<Target>(this, k, Target::_constructor_tag{});

View File

@@ -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<Project *()> &projectGetter);
QList<Utils::Store> 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();

View File

@@ -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<Project> 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 "<html>"
+ 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:")
+ "<ul><li>"
+ 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.")
+ "</li><li>" + Tr::tr("Copy the build, deploy, and run steps to another kit.")
+ "</li></ul></html>";
}
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<Kit *> 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<QMenu *>();
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<Project> m_project;
};
// Standard third level for the generic case: i.e. all except for the Build/Run page
class MiscSettingsPanelItem : public TreeItem // TypedTreeItem<TreeItem, MiscSettingsGroupItem>
{
@@ -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<void ()> m_changeListener;
};

View File

@@ -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;
}

View File

@@ -147,6 +147,7 @@ signals:
private:
bool fromMap(const Utils::Store &map);
bool addConfigurationsFromMap(const Utils::Store &map, bool setActiveConfigurations);
void updateDeviceState();