EnvironmentWidget: Add convenient way to add to path list

Sometimes, users want to add entries into environment variables such as
PATH, which can get a bit fiddly when doing it by hand.
So let's add convenience UI elements for adding directories to such
variables.

Change-Id: I81c71ea0052893ff0a8175508b71cd73d83dd849
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2019-03-12 17:56:24 +01:00
parent 632d64fdc7
commit 1fcb8a56e0
6 changed files with 113 additions and 5 deletions

View File

@@ -43,7 +43,8 @@ BuildEnvironmentWidget::BuildEnvironmentWidget(BuildConfiguration *bc) :
m_clearSystemEnvironmentCheckBox = new QCheckBox(this); m_clearSystemEnvironmentCheckBox = new QCheckBox(this);
m_clearSystemEnvironmentCheckBox->setText(tr("Clear system environment")); m_clearSystemEnvironmentCheckBox->setText(tr("Clear system environment"));
m_buildEnvironmentWidget = new EnvironmentWidget(this, m_clearSystemEnvironmentCheckBox); m_buildEnvironmentWidget = new EnvironmentWidget(this, EnvironmentWidget::TypeLocal,
m_clearSystemEnvironmentCheckBox);
vbox->addWidget(m_buildEnvironmentWidget); vbox->addWidget(m_buildEnvironmentWidget);
connect(m_buildEnvironmentWidget, &EnvironmentWidget::userChangesChanged, connect(m_buildEnvironmentWidget, &EnvironmentWidget::userChangesChanged,

View File

@@ -66,6 +66,8 @@ public:
using EnvironmentModifier = std::function<void(Utils::Environment &)>; using EnvironmentModifier = std::function<void(Utils::Environment &)>;
void addModifier(const EnvironmentModifier &); void addModifier(const EnvironmentModifier &);
bool isLocal() const { return m_isLocal; }
signals: signals:
void baseEnvironmentChanged(); void baseEnvironmentChanged();
void userEnvironmentChangesChanged(const QList<Utils::EnvironmentItem> &diff); void userEnvironmentChangesChanged(const QList<Utils::EnvironmentItem> &diff);
@@ -75,6 +77,8 @@ protected:
void fromMap(const QVariantMap &map) override; void fromMap(const QVariantMap &map) override;
void toMap(QVariantMap &map) const override; void toMap(QVariantMap &map) const override;
void setIsLocal(bool local) { m_isLocal = local; }
private: private:
// One possible choice in the Environment aspect. // One possible choice in the Environment aspect.
struct BaseEnvironment { struct BaseEnvironment {
@@ -84,10 +88,11 @@ private:
QString displayName; QString displayName;
}; };
int m_base = -1;
QList<Utils::EnvironmentItem> m_userChanges; QList<Utils::EnvironmentItem> m_userChanges;
QList<EnvironmentModifier> m_modifiers; QList<EnvironmentModifier> m_modifiers;
QList<BaseEnvironment> m_baseEnvironments; QList<BaseEnvironment> m_baseEnvironments;
int m_base = -1;
bool m_isLocal = false;
}; };
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -72,7 +72,9 @@ EnvironmentAspectWidget::EnvironmentAspectWidget(EnvironmentAspect *aspect, QWid
if (additionalWidget) if (additionalWidget)
baseLayout->addWidget(additionalWidget); baseLayout->addWidget(additionalWidget);
m_environmentWidget = new EnvironmentWidget(this, baseEnvironmentWidget); const EnvironmentWidget::Type widgetType = aspect->isLocal()
? EnvironmentWidget::TypeLocal : EnvironmentWidget::TypeRemote;
m_environmentWidget = new EnvironmentWidget(this, widgetType, baseEnvironmentWidget);
m_environmentWidget->setBaseEnvironment(m_aspect->currentUnmodifiedBaseEnvironment()); m_environmentWidget->setBaseEnvironment(m_aspect->currentUnmodifiedBaseEnvironment());
m_environmentWidget->setBaseEnvironmentText(m_aspect->currentDisplayName()); m_environmentWidget->setBaseEnvironmentText(m_aspect->currentDisplayName());
m_environmentWidget->setUserChanges(m_aspect->userEnvironmentChanges()); m_environmentWidget->setUserChanges(m_aspect->userEnvironmentChanges());

View File

@@ -33,10 +33,13 @@
#include <utils/environmentmodel.h> #include <utils/environmentmodel.h>
#include <utils/environmentdialog.h> #include <utils/environmentdialog.h>
#include <utils/headerviewstretcher.h> #include <utils/headerviewstretcher.h>
#include <utils/hostosinfo.h>
#include <utils/itemviews.h> #include <utils/itemviews.h>
#include <utils/tooltip/tooltip.h> #include <utils/tooltip/tooltip.h>
#include <QDir> #include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QString> #include <QString>
#include <QPushButton> #include <QPushButton>
#include <QTreeView> #include <QTreeView>
@@ -131,10 +134,12 @@ public:
QPushButton *m_resetButton; QPushButton *m_resetButton;
QPushButton *m_unsetButton; QPushButton *m_unsetButton;
QPushButton *m_batchEditButton; QPushButton *m_batchEditButton;
QPushButton *m_appendPathButton = nullptr;
QPushButton *m_prependPathButton = nullptr;
QPushButton *m_terminalButton; QPushButton *m_terminalButton;
}; };
EnvironmentWidget::EnvironmentWidget(QWidget *parent, QWidget *additionalDetailsWidget) EnvironmentWidget::EnvironmentWidget(QWidget *parent, Type type, QWidget *additionalDetailsWidget)
: QWidget(parent), d(std::make_unique<EnvironmentWidgetPrivate>()) : QWidget(parent), d(std::make_unique<EnvironmentWidgetPrivate>())
{ {
d->m_model = new Utils::EnvironmentModel(); d->m_model = new Utils::EnvironmentModel();
@@ -200,6 +205,21 @@ EnvironmentWidget::EnvironmentWidget(QWidget *parent, QWidget *additionalDetails
d->m_unsetButton->setText(tr("&Unset")); d->m_unsetButton->setText(tr("&Unset"));
buttonLayout->addWidget(d->m_unsetButton); buttonLayout->addWidget(d->m_unsetButton);
if (type == TypeLocal) {
d->m_appendPathButton = new QPushButton(this);
d->m_appendPathButton->setEnabled(false);
d->m_appendPathButton->setText(tr("Append Path..."));
buttonLayout->addWidget(d->m_appendPathButton);
d->m_prependPathButton = new QPushButton(this);
d->m_prependPathButton->setEnabled(false);
d->m_prependPathButton->setText(tr("Prepend Path..."));
buttonLayout->addWidget(d->m_prependPathButton);
connect(d->m_appendPathButton, &QAbstractButton::clicked,
this, &EnvironmentWidget::appendPathButtonClicked);
connect(d->m_prependPathButton, &QAbstractButton::clicked,
this, &EnvironmentWidget::prependPathButtonClicked);
}
d->m_batchEditButton = new QPushButton(this); d->m_batchEditButton = new QPushButton(this);
d->m_batchEditButton->setText(tr("&Batch Edit...")); d->m_batchEditButton->setText(tr("&Batch Edit..."));
buttonLayout->addWidget(d->m_batchEditButton); buttonLayout->addWidget(d->m_batchEditButton);
@@ -318,6 +338,36 @@ void EnvironmentWidget::linkActivated(const QString &link)
focusIndex(idx); focusIndex(idx);
} }
bool EnvironmentWidget::currentEntryIsPathList(const QModelIndex &current) const
{
if (!current.isValid())
return false;
// Look at the name first and check it against some well-known path variables. Extend as needed.
const QString varName = d->m_model->indexToVariable(current);
if (varName.compare("PATH", Utils::HostOsInfo::fileNameCaseSensitivity()) == 0)
return true;
if (Utils::HostOsInfo::isMacHost() && varName == "DYLD_LIBRARY_PATH")
return true;
if (Utils::HostOsInfo::isAnyUnixHost() && varName == "LD_LIBRARY_PATH")
return true;
// Now check the value: If it's a list of strings separated by the platform's path separator
// and at least one of the strings is an existing directory, then that's enough proof for us.
QModelIndex valueIndex = current;
if (valueIndex.column() == 0)
valueIndex = valueIndex.siblingAtColumn(1);
const QStringList entries = d->m_model->data(valueIndex).toString()
.split(Utils::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts);
if (entries.length() < 2)
return false;
for (const QString &potentialDir : entries) {
if (QFileInfo(potentialDir).isDir())
return true;
}
return false;
}
void EnvironmentWidget::updateButtons() void EnvironmentWidget::updateButtons()
{ {
environmentCurrentIndexChanged(d->m_environmentView->currentIndex()); environmentCurrentIndexChanged(d->m_environmentView->currentIndex());
@@ -352,6 +402,42 @@ void EnvironmentWidget::unsetEnvironmentButtonClicked()
d->m_model->unsetVariable(name); d->m_model->unsetVariable(name);
} }
void EnvironmentWidget::amendPathList(const PathListModifier &modifier)
{
const QString varName = d->m_model->indexToVariable(d->m_environmentView->currentIndex());
const QString dir = QDir::toNativeSeparators(
QFileDialog::getExistingDirectory(this, tr("Choose a directory")));
if (dir.isEmpty())
return;
QModelIndex index = d->m_model->variableToIndex(varName);
if (!index.isValid())
return;
if (index.column() == 0)
index = index.siblingAtColumn(1);
const QString value = d->m_model->data(index).toString();
d->m_model->setData(index, modifier(value, dir));
}
void EnvironmentWidget::appendPathButtonClicked()
{
amendPathList([](const QString &pathList, const QString &dir) {
QString newPathList = dir;
if (!pathList.isEmpty())
newPathList.prepend(Utils::HostOsInfo::pathListSeparator()).prepend(pathList);
return newPathList;
});
}
void EnvironmentWidget::prependPathButtonClicked()
{
amendPathList([](const QString &pathList, const QString &dir) {
QString newPathList = dir;
if (!pathList.isEmpty())
newPathList.append(Utils::HostOsInfo::pathListSeparator()).append(pathList);
return newPathList;
});
}
void EnvironmentWidget::batchEditEnvironmentButtonClicked() void EnvironmentWidget::batchEditEnvironmentButtonClicked()
{ {
const QList<Utils::EnvironmentItem> changes = d->m_model->userChanges(); const QList<Utils::EnvironmentItem> changes = d->m_model->userChanges();
@@ -385,6 +471,10 @@ void EnvironmentWidget::environmentCurrentIndexChanged(const QModelIndex &curren
d->m_resetButton->setEnabled(false); d->m_resetButton->setEnabled(false);
d->m_unsetButton->setEnabled(false); d->m_unsetButton->setEnabled(false);
} }
if (d->m_appendPathButton) {
d->m_appendPathButton->setEnabled(currentEntryIsPathList(current));
d->m_prependPathButton->setEnabled(currentEntryIsPathList(current));
}
} }
void EnvironmentWidget::invalidateCurrentIndex() void EnvironmentWidget::invalidateCurrentIndex()

View File

@@ -29,6 +29,7 @@
#include <QWidget> #include <QWidget>
#include <functional>
#include <memory> #include <memory>
QT_FORWARD_DECLARE_CLASS(QModelIndex) QT_FORWARD_DECLARE_CLASS(QModelIndex)
@@ -47,7 +48,9 @@ class PROJECTEXPLORER_EXPORT EnvironmentWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
explicit EnvironmentWidget(QWidget *parent, QWidget *additionalDetailsWidget = nullptr); enum Type { TypeLocal, TypeRemote };
explicit EnvironmentWidget(QWidget *parent, Type type,
QWidget *additionalDetailsWidget = nullptr);
~EnvironmentWidget() override; ~EnvironmentWidget() override;
void setBaseEnvironmentText(const QString &text); void setBaseEnvironmentText(const QString &text);
@@ -65,6 +68,8 @@ private:
void addEnvironmentButtonClicked(); void addEnvironmentButtonClicked();
void removeEnvironmentButtonClicked(); void removeEnvironmentButtonClicked();
void unsetEnvironmentButtonClicked(); void unsetEnvironmentButtonClicked();
void appendPathButtonClicked();
void prependPathButtonClicked();
void batchEditEnvironmentButtonClicked(); void batchEditEnvironmentButtonClicked();
void openTerminal(); void openTerminal();
void environmentCurrentIndexChanged(const QModelIndex &current); void environmentCurrentIndexChanged(const QModelIndex &current);
@@ -73,6 +78,10 @@ private:
void focusIndex(const QModelIndex &index); void focusIndex(const QModelIndex &index);
void updateButtons(); void updateButtons();
void linkActivated(const QString &link); void linkActivated(const QString &link);
bool currentEntryIsPathList(const QModelIndex &current) const;
using PathListModifier = std::function<QString(const QString &oldList, const QString &newDir)>;
void amendPathList(const PathListModifier &modifier);
const std::unique_ptr<EnvironmentWidgetPrivate> d; const std::unique_ptr<EnvironmentWidgetPrivate> d;
}; };

View File

@@ -38,6 +38,7 @@ namespace ProjectExplorer {
LocalEnvironmentAspect::LocalEnvironmentAspect(Target *target) LocalEnvironmentAspect::LocalEnvironmentAspect(Target *target)
{ {
setIsLocal(true);
addSupportedBaseEnvironment(tr("Clean Environment"), {}); addSupportedBaseEnvironment(tr("Clean Environment"), {});
addSupportedBaseEnvironment(tr("System Environment"), [] { addSupportedBaseEnvironment(tr("System Environment"), [] {