ProjectExplorer: Let users clone one run config into another

This is particularly useful now that we don't sync the run configs
anymore between build configurations of the same target.

Change-Id: Ib434d0414895ea0d80a41f73694674f16704d227
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2025-04-22 14:36:37 +02:00
parent 638f7f421a
commit c2355efbe1
4 changed files with 162 additions and 7 deletions

View File

@@ -28,6 +28,7 @@
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
#include <utils/processinterface.h> #include <utils/processinterface.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <utils/variablechooser.h> #include <utils/variablechooser.h>
#include <QComboBox> #include <QComboBox>
@@ -417,6 +418,23 @@ RunConfiguration *RunConfiguration::clone(BuildConfiguration *bc)
return RunConfigurationFactory::restore(bc, map); return RunConfigurationFactory::restore(bc, map);
} }
void RunConfiguration::cloneFromOther(const RunConfiguration *rc)
{
Store map;
rc->toMap(map);
fromMap(map);
// Same build config? Then uniquify the name.
if (rc->buildConfiguration() == buildConfiguration()) {
QList<RunConfiguration *> others = buildConfiguration()->runConfigurations();
others.removeOne(this);
const QStringList otherNames = Utils::transform(others, [](const RunConfiguration *rc) {
return rc->displayName();
});
setDisplayName(makeUniquelyNumbered(rc->displayName(), otherNames));
}
}
BuildTargetInfo RunConfiguration::buildTargetInfo() const BuildTargetInfo RunConfiguration::buildTargetInfo() const
{ {
BuildSystem *bs = buildSystem(); BuildSystem *bs = buildSystem();

View File

@@ -173,6 +173,7 @@ public:
void update(); void update();
virtual RunConfiguration *clone(BuildConfiguration *bc); virtual RunConfiguration *clone(BuildConfiguration *bc);
void cloneFromOther(const RunConfiguration *rc);
BuildConfiguration *buildConfiguration() const { return m_buildConfiguration; } BuildConfiguration *buildConfiguration() const { return m_buildConfiguration; }

View File

@@ -7,6 +7,7 @@
#include "buildmanager.h" #include "buildmanager.h"
#include "buildstepspage.h" #include "buildstepspage.h"
#include "deployconfiguration.h" #include "deployconfiguration.h"
#include "project.h"
#include "projectconfigurationmodel.h" #include "projectconfigurationmodel.h"
#include "projectexplorerconstants.h" #include "projectexplorerconstants.h"
#include "projectexplorertr.h" #include "projectexplorertr.h"
@@ -14,23 +15,131 @@
#include "target.h" #include "target.h"
#include <utils/guiutils.h> #include <utils/guiutils.h>
#include <utils/itemviews.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
#include <utils/treemodel.h>
#include <QAction> #include <QAction>
#include <QComboBox> #include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout> #include <QGridLayout>
#include <QHash>
#include <QInputDialog> #include <QInputDialog>
#include <QLabel> #include <QLabel>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QPointer>
#include <QPushButton> #include <QPushButton>
#include <QSpacerItem> #include <QSpacerItem>
#include <QWidget> #include <QWidget>
using namespace Utils;
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
namespace {
class CloneIntoRunConfigDialog : public QDialog
{
public:
CloneIntoRunConfigDialog(const RunConfiguration *thisRc)
: m_rcModel(new RCModel), m_rcView(new TreeView(this))
{
// Collect run configurations.
using RCList = QList<const RunConfiguration *>;
using RCsPerBuildConfig = QHash<const BuildConfiguration *, RCList>;
QHash<const Target *, RCsPerBuildConfig> eligibleRcs;
for (const Target * const t : thisRc->project()->targets()) {
RCsPerBuildConfig rcsForTarget;
for (const BuildConfiguration * const bc : t->buildConfigurations()) {
RCList rcsForBuildConfig;
for (const RunConfiguration * const rc : bc->runConfigurations()) {
if (rc != thisRc && rc->buildKey() == thisRc->buildKey())
rcsForBuildConfig << rc;
}
if (!rcsForBuildConfig.isEmpty())
rcsForTarget.insert(bc, rcsForBuildConfig);
}
if (!rcsForTarget.isEmpty())
eligibleRcs.insert(t, rcsForTarget);
}
// Initialize model. Only use static data. This way, we are immune
// to removal of any configurations while the dialog is running.
if (eligibleRcs.isEmpty()) {
m_rcModel->rootItem()->appendChild(new StaticTreeItem(
Tr::tr("There are no other run configurations for this application.")));
} else {
for (auto targetIt = eligibleRcs.cbegin(); targetIt != eligibleRcs.cend(); ++targetIt) {
const auto targetItem = new StaticTreeItem(targetIt.key()->displayName());
for (auto bcIt = targetIt.value().cbegin(); bcIt != targetIt.value().cend();
++bcIt) {
const auto bcItem = new StaticTreeItem(bcIt.key()->displayName());
for (const RunConfiguration *const rc : std::as_const(bcIt.value()))
bcItem->appendChild(new RCTreeItem(rc));
targetItem->appendChild(bcItem);
}
m_rcModel->rootItem()->appendChild(targetItem);
}
}
// UI
m_rcView->setModel(m_rcModel);
m_rcView->expandAll();
setWindowTitle(Tr::tr("Clone From Run Configuration"));
m_rcView->setSelectionMode(TreeView::SingleSelection);
m_rcView->setSelectionBehavior(TreeView::SelectItems);
m_rcView->setSortingEnabled(true);
m_rcView->resizeColumnToContents(0);
m_rcView->sortByColumn(0, Qt::AscendingOrder);
const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttonBox->button(QDialogButtonBox::Ok)->setText(Tr::tr("Clone"));
connect(m_rcView, &TreeView::doubleClicked, this, [this] { accept(); });
const auto updateOkButton = [buttonBox, this] {
buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(isRcItem(m_rcView->selectionModel()->currentIndex()));
};
connect(m_rcView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, updateOkButton);
updateOkButton();
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
const auto layout = new QVBoxLayout(this);
layout->addWidget(m_rcView);
layout->addWidget(buttonBox);
}
const RunConfiguration *source() const { return m_source; }
private:
class RCTreeItem : public StaticTreeItem
{
public:
RCTreeItem(const RunConfiguration *rc): StaticTreeItem(rc->displayName()), m_rc(rc) {}
const RunConfiguration *runConfig() const { return m_rc; }
private:
QPointer<const RunConfiguration> m_rc;
};
void accept() override
{
const QModelIndex current = m_rcView->selectionModel()->currentIndex();
QTC_ASSERT(isRcItem(current), return);
const auto item = dynamic_cast<RCTreeItem *>(m_rcModel->itemForIndex(current));
QTC_ASSERT(item, return);
m_source = item->runConfig();
QDialog::accept();
}
bool isRcItem(const QModelIndex &index) const { return index.parent().parent().isValid(); }
using RCModel = TreeModel<TreeItem, StaticTreeItem, StaticTreeItem, RCTreeItem>;
RCModel * const m_rcModel;
TreeView * const m_rcView;
const RunConfiguration * m_source = nullptr;
};
} // namespace
// RunSettingsWidget // RunSettingsWidget
@@ -57,6 +166,7 @@ RunSettingsWidget::RunSettingsWidget(Target *target) :
m_removeAllRunConfigsButton = new QPushButton(Tr::tr("Remove All"), this); m_removeAllRunConfigsButton = new QPushButton(Tr::tr("Remove All"), this);
m_renameRunButton = new QPushButton(Tr::tr("Rename..."), this); m_renameRunButton = new QPushButton(Tr::tr("Rename..."), this);
m_cloneRunButton = new QPushButton(Tr::tr("Clone..."), this); m_cloneRunButton = new QPushButton(Tr::tr("Clone..."), this);
m_cloneIntoThisButton = new QPushButton(Tr::tr("Clone Into This..."), this);
auto spacer1 = new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum); auto spacer1 = new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum);
auto spacer2 = new QSpacerItem(10, 10, QSizePolicy::Minimum, QSizePolicy::Expanding); auto spacer2 = new QSpacerItem(10, 10, QSizePolicy::Minimum, QSizePolicy::Expanding);
@@ -94,7 +204,8 @@ RunSettingsWidget::RunSettingsWidget(Target *target) :
m_gridLayout->addWidget(m_removeAllRunConfigsButton, 4, 4, 1, 1); m_gridLayout->addWidget(m_removeAllRunConfigsButton, 4, 4, 1, 1);
m_gridLayout->addWidget(m_renameRunButton, 4, 5, 1, 1); m_gridLayout->addWidget(m_renameRunButton, 4, 5, 1, 1);
m_gridLayout->addWidget(m_cloneRunButton, 4, 6, 1, 1); m_gridLayout->addWidget(m_cloneRunButton, 4, 6, 1, 1);
m_gridLayout->addItem(spacer1, 4, 7, 1, 1); m_gridLayout->addWidget(m_cloneIntoThisButton, 4, 7, 1, 1);
m_gridLayout->addItem(spacer1, 4, 8, 1, 1);
m_gridLayout->addWidget(runWidget, 5, 0, 1, -1); m_gridLayout->addWidget(runWidget, 5, 0, 1, -1);
m_gridLayout->addItem(spacer2, 6, 0, 1, 1); m_gridLayout->addItem(spacer2, 6, 0, 1, 1);
@@ -140,6 +251,8 @@ RunSettingsWidget::RunSettingsWidget(Target *target) :
this, &RunSettingsWidget::renameRunConfiguration); this, &RunSettingsWidget::renameRunConfiguration);
connect(m_cloneRunButton, &QAbstractButton::clicked, connect(m_cloneRunButton, &QAbstractButton::clicked,
this, &RunSettingsWidget::cloneRunConfiguration); this, &RunSettingsWidget::cloneRunConfiguration);
connect(m_cloneIntoThisButton, &QAbstractButton::clicked,
this, &RunSettingsWidget::cloneOtherRunConfiguration);
connect(m_target, &Target::addedRunConfiguration, connect(m_target, &Target::addedRunConfiguration,
this, &RunSettingsWidget::updateRemoveToolButtons); this, &RunSettingsWidget::updateRemoveToolButtons);
@@ -194,6 +307,22 @@ void RunSettingsWidget::cloneRunConfiguration()
m_target->activeBuildConfiguration()->setActiveRunConfiguration(newRc); m_target->activeBuildConfiguration()->setActiveRunConfiguration(newRc);
} }
void RunSettingsWidget::cloneOtherRunConfiguration()
{
// Paranoia: Guard against project changes during the dialog run.
const QPointer<RunConfiguration> thisRc = m_target->activeRunConfiguration();
const QPointer<BuildConfiguration> bc(thisRc->buildConfiguration());
CloneIntoRunConfigDialog dlg(m_target->activeRunConfiguration());
if (dlg.exec() == QDialog::Accepted) {
const RunConfiguration * const otherRc = dlg.source();
if (bc && otherRc && thisRc && thisRc == m_target->activeRunConfiguration()) {
thisRc->cloneFromOther(otherRc);
setConfigurationWidget(thisRc, true); // Force visual update.
}
}
}
void RunSettingsWidget::removeRunConfiguration() void RunSettingsWidget::removeRunConfiguration()
{ {
RunConfiguration *rc = m_target->activeRunConfiguration(); RunConfiguration *rc = m_target->activeRunConfiguration();
@@ -209,6 +338,7 @@ void RunSettingsWidget::removeRunConfiguration()
updateRemoveToolButtons(); updateRemoveToolButtons();
m_renameRunButton->setEnabled(m_target->activeRunConfiguration()); m_renameRunButton->setEnabled(m_target->activeRunConfiguration());
m_cloneRunButton->setEnabled(m_target->activeRunConfiguration()); m_cloneRunButton->setEnabled(m_target->activeRunConfiguration());
m_cloneIntoThisButton->setEnabled(m_target->activeRunConfiguration());
} }
void RunSettingsWidget::removeAllRunConfigurations() void RunSettingsWidget::removeAllRunConfigurations()
@@ -228,6 +358,7 @@ void RunSettingsWidget::removeAllRunConfigurations()
updateRemoveToolButtons(); updateRemoveToolButtons();
m_renameRunButton->setEnabled(false); m_renameRunButton->setEnabled(false);
m_cloneRunButton->setEnabled(false); m_cloneRunButton->setEnabled(false);
m_cloneIntoThisButton->setEnabled(false);
} }
void RunSettingsWidget::activeRunConfigurationChanged() void RunSettingsWidget::activeRunConfigurationChanged()
@@ -240,10 +371,12 @@ void RunSettingsWidget::activeRunConfigurationChanged()
{ {
const Utils::GuardLocker locker(m_ignoreChanges); const Utils::GuardLocker locker(m_ignoreChanges);
m_runConfigurationCombo->setCurrentIndex(index); m_runConfigurationCombo->setCurrentIndex(index);
setConfigurationWidget(qobject_cast<RunConfiguration *>(model->projectConfigurationAt(index))); setConfigurationWidget(
qobject_cast<RunConfiguration *>(model->projectConfigurationAt(index)), false);
} }
m_renameRunButton->setEnabled(m_target->activeRunConfiguration()); m_renameRunButton->setEnabled(m_target->activeRunConfiguration());
m_cloneRunButton->setEnabled(m_target->activeRunConfiguration()); m_cloneRunButton->setEnabled(m_target->activeRunConfiguration());
m_cloneIntoThisButton->setEnabled(m_target->activeRunConfiguration());
} }
void RunSettingsWidget::renameRunConfiguration() void RunSettingsWidget::renameRunConfiguration()
@@ -283,7 +416,7 @@ void RunSettingsWidget::currentRunConfigurationChanged(int index)
} }
// Update the run configuration configuration widget // Update the run configuration configuration widget
setConfigurationWidget(selectedRunConfiguration); setConfigurationWidget(selectedRunConfiguration, false);
} }
void RunSettingsWidget::currentDeployConfigurationChanged(int index) void RunSettingsWidget::currentDeployConfigurationChanged(int index)
@@ -401,8 +534,9 @@ void RunSettingsWidget::initForActiveBuildConfig()
updateRemoveToolButtons(); updateRemoveToolButtons();
m_renameRunButton->setEnabled(rc); m_renameRunButton->setEnabled(rc);
m_cloneRunButton->setEnabled(rc); m_cloneRunButton->setEnabled(rc);
m_cloneIntoThisButton->setEnabled(rc);
setConfigurationWidget(rc); setConfigurationWidget(rc, false);
} }
void RunSettingsWidget::updateRemoveToolButtons() void RunSettingsWidget::updateRemoveToolButtons()
@@ -449,9 +583,9 @@ void RunSettingsWidget::updateDeployConfiguration(DeployConfiguration *dc)
m_deployLayout->addWidget(m_deploySteps); m_deployLayout->addWidget(m_deploySteps);
} }
void RunSettingsWidget::setConfigurationWidget(RunConfiguration *rc) void RunSettingsWidget::setConfigurationWidget(RunConfiguration *rc, bool force)
{ {
if (rc == m_runConfiguration) if (!force && rc == m_runConfiguration)
return; return;
delete m_runConfigurationWidget; delete m_runConfigurationWidget;

View File

@@ -40,6 +40,7 @@ private:
void currentRunConfigurationChanged(int index); void currentRunConfigurationChanged(int index);
void showAddRunConfigDialog(); void showAddRunConfigDialog();
void cloneRunConfiguration(); void cloneRunConfiguration();
void cloneOtherRunConfiguration();
void removeRunConfiguration(); void removeRunConfiguration();
void removeAllRunConfigurations(); void removeAllRunConfigurations();
void activeRunConfigurationChanged(); void activeRunConfigurationChanged();
@@ -56,7 +57,7 @@ private:
QString uniqueDCName(const QString &name); QString uniqueDCName(const QString &name);
QString uniqueRCName(const QString &name); QString uniqueRCName(const QString &name);
void updateDeployConfiguration(DeployConfiguration *); void updateDeployConfiguration(DeployConfiguration *);
void setConfigurationWidget(RunConfiguration *rc); void setConfigurationWidget(RunConfiguration *rc, bool force);
void addRunControlWidgets(); void addRunControlWidgets();
void addSubWidget(QWidget *subWidget, QLabel *label); void addSubWidget(QWidget *subWidget, QLabel *label);
@@ -87,6 +88,7 @@ private:
QPushButton *m_removeAllRunConfigsButton; QPushButton *m_removeAllRunConfigsButton;
QPushButton *m_renameRunButton; QPushButton *m_renameRunButton;
QPushButton *m_cloneRunButton; QPushButton *m_cloneRunButton;
QPushButton *m_cloneIntoThisButton;
QPushButton *m_renameDeployButton; QPushButton *m_renameDeployButton;
Utils::InfoLabel *m_disabledText; Utils::InfoLabel *m_disabledText;
}; };