diff --git a/src/plugins/python/CMakeLists.txt b/src/plugins/python/CMakeLists.txt
index 4036e919b38..7dd1f2fab5d 100644
--- a/src/plugins/python/CMakeLists.txt
+++ b/src/plugins/python/CMakeLists.txt
@@ -1,11 +1,13 @@
add_qtc_plugin(Python
PLUGIN_DEPENDS Core QtSupport ProjectExplorer TextEditor
SOURCES
+ python.qrc
pythoneditor.cpp pythoneditor.h
pythonconstants.h
pythonplugin.cpp pythonplugin.h
pythonformattoken.h
pythonhighlighter.cpp pythonhighlighter.h
pythonindenter.cpp pythonindenter.h
+ pythonsettings.cpp pythonsettings.h
pythonscanner.cpp pythonscanner.h
)
diff --git a/src/plugins/python/images/settingscategory_python.png b/src/plugins/python/images/settingscategory_python.png
new file mode 100644
index 00000000000..36ad4c2c3c8
Binary files /dev/null and b/src/plugins/python/images/settingscategory_python.png differ
diff --git a/src/plugins/python/images/settingscategory_python@2x.png b/src/plugins/python/images/settingscategory_python@2x.png
new file mode 100644
index 00000000000..84f7819d861
Binary files /dev/null and b/src/plugins/python/images/settingscategory_python@2x.png differ
diff --git a/src/plugins/python/python.pro b/src/plugins/python/python.pro
index 8e2b9c3d821..788d4b142b8 100644
--- a/src/plugins/python/python.pro
+++ b/src/plugins/python/python.pro
@@ -11,10 +11,15 @@ HEADERS += \
pythonindenter.h \
pythonformattoken.h \
pythonscanner.h \
+ pythonsettings.h
SOURCES += \
pythonplugin.cpp \
pythoneditor.cpp \
pythonhighlighter.cpp \
pythonindenter.cpp \
- pythonscanner.cpp
+ pythonscanner.cpp \
+ pythonsettings.cpp
+
+RESOURCES += \
+ python.qrc
diff --git a/src/plugins/python/python.qbs b/src/plugins/python/python.qbs
index d04b0dac437..f45d4a0fb79 100644
--- a/src/plugins/python/python.qbs
+++ b/src/plugins/python/python.qbs
@@ -14,6 +14,7 @@ QtcPlugin {
Group {
name: "General"
files: [
+ "python.qrc",
"pythoneditor.cpp", "pythoneditor.h",
"pythonconstants.h",
"pythonplugin.cpp", "pythonplugin.h",
@@ -21,6 +22,7 @@ QtcPlugin {
"pythonindenter.cpp", "pythonindenter.h",
"pythonformattoken.h",
"pythonscanner.h", "pythonscanner.cpp",
+ "pythonsettings.cpp", "pythonsettings.h",
]
}
}
diff --git a/src/plugins/python/python.qrc b/src/plugins/python/python.qrc
new file mode 100644
index 00000000000..1a6da2a242b
--- /dev/null
+++ b/src/plugins/python/python.qrc
@@ -0,0 +1,6 @@
+
+
+ images/settingscategory_python.png
+ images/settingscategory_python@2x.png
+
+
diff --git a/src/plugins/python/pythonconstants.h b/src/plugins/python/pythonconstants.h
index fbea15eed49..0fba0603e92 100644
--- a/src/plugins/python/pythonconstants.h
+++ b/src/plugins/python/pythonconstants.h
@@ -34,6 +34,9 @@ const char C_PYTHONEDITOR_ID[] = "PythonEditor.PythonEditor";
const char C_EDITOR_DISPLAY_NAME[] =
QT_TRANSLATE_NOOP("OpenWith::Editors", "Python Editor");
+const char C_PYTHONOPTIONS_PAGE_ID[] = "PythonEditor.OptionsPage";
+const char C_PYTHON_SETTINGS_CATEGORY[] = "P.Python";
+
/*******************************************************************************
* MIME type
******************************************************************************/
diff --git a/src/plugins/python/pythonplugin.cpp b/src/plugins/python/pythonplugin.cpp
index 93635d7dc77..3dff835fc9b 100644
--- a/src/plugins/python/pythonplugin.cpp
+++ b/src/plugins/python/pythonplugin.cpp
@@ -27,6 +27,7 @@
#include "pythoneditor.h"
#include "pythonconstants.h"
#include "pythonhighlighter.h"
+#include "pythonsettings.h"
#include
#include
@@ -56,16 +57,19 @@
#include
#include
+#include
#include
-#include
-#include
-#include
-#include
+#include
+#include
#include
#include
#include
#include
-#include
+#include
+#include
+#include
+#include
+#include
using namespace Core;
using namespace ProjectExplorer;
@@ -217,14 +221,103 @@ private:
////////////////////////////////////////////////////////////////
-class InterpreterAspect : public BaseStringAspect
+class InterpreterAspect : public ProjectConfigurationAspect
{
Q_OBJECT
public:
InterpreterAspect() = default;
+
+ Interpreter currentInterpreter() const;
+ void updateInterpreters(const QList &interpreters);
+ void setDefaultInterpreter(const Interpreter &interpreter) { m_defaultId = interpreter.id; }
+
+ void fromMap(const QVariantMap &) override;
+ void toMap(QVariantMap &) const override;
+ void addToConfigurationLayout(QFormLayout *layout) override;
+
+private:
+ void updateCurrentInterpreter();
+ void updateComboBox();
+ QList m_interpreters;
+ QPointer m_comboBox;
+ QString m_defaultId;
+ QString m_currentId;
};
+Interpreter InterpreterAspect::currentInterpreter() const
+{
+ return m_comboBox ? m_interpreters.value(m_comboBox->currentIndex()) : Interpreter();
+}
+
+void InterpreterAspect::updateInterpreters(const QList &interpreters)
+{
+ m_interpreters = interpreters;
+ if (m_comboBox)
+ updateComboBox();
+}
+
+void InterpreterAspect::fromMap(const QVariantMap &map)
+{
+ m_currentId = map.value(settingsKey(), m_defaultId).toString();
+}
+
+void InterpreterAspect::toMap(QVariantMap &map) const
+{
+ map.insert(settingsKey(), m_currentId);
+}
+
+void InterpreterAspect::addToConfigurationLayout(QFormLayout *layout)
+{
+ if (QTC_GUARD(m_comboBox.isNull()))
+ m_comboBox = new QComboBox;
+
+ updateComboBox();
+ connect(m_comboBox,
+ &QComboBox::currentTextChanged,
+ this,
+ &InterpreterAspect::updateCurrentInterpreter);
+
+ auto manageButton = new QPushButton(tr("Manage..."));
+ connect(manageButton, &QPushButton::clicked, []() {
+ Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID);
+ });
+
+ auto rowLayout = new QHBoxLayout;
+ rowLayout->addWidget(m_comboBox);
+ rowLayout->addWidget(manageButton);
+ layout->addRow(tr("Interpreter"), rowLayout);
+}
+
+void InterpreterAspect::updateCurrentInterpreter()
+{
+ m_currentId = currentInterpreter().id;
+ m_comboBox->setToolTip(currentInterpreter().command.toUserOutput());
+ emit changed();
+}
+
+void InterpreterAspect::updateComboBox()
+{
+ int currentIndex = -1;
+ int defaultIndex = -1;
+ const QString currentId = m_currentId;
+ m_comboBox->clear();
+ for (const Interpreter &interpreter : m_interpreters) {
+ int index = m_comboBox->count();
+ m_comboBox->addItem(interpreter.name);
+ m_comboBox->setItemData(index, interpreter.command.toUserOutput(), Qt::ToolTipRole);
+ if (interpreter.id == currentId)
+ currentIndex = index;
+ if (interpreter.id == m_defaultId)
+ defaultIndex = index;
+ }
+ if (currentIndex >= 0)
+ m_comboBox->setCurrentIndex(currentIndex);
+ else if (defaultIndex >= 0)
+ m_comboBox->setCurrentIndex(defaultIndex);
+ updateCurrentInterpreter();
+}
+
class MainScriptAspect : public BaseStringAspect
{
Q_OBJECT
@@ -251,7 +344,7 @@ private:
bool supportsDebugger() const { return true; }
QString mainScript() const { return aspect()->value(); }
QString arguments() const { return aspect()->arguments(macroExpander()); }
- QString interpreter() const { return aspect()->value(); }
+ QString interpreter() const { return aspect()->currentInterpreter().command.toString(); }
void updateTargetInformation();
};
@@ -259,15 +352,14 @@ private:
PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
: RunConfiguration(target, id)
{
- const Environment sysEnv = Environment::systemEnvironment();
- const QString exec = sysEnv.searchInPath("python").toString();
-
auto interpreterAspect = addAspect();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
- interpreterAspect->setLabelText(tr("Interpreter:"));
- interpreterAspect->setDisplayStyle(BaseStringAspect::PathChooserDisplay);
- interpreterAspect->setHistoryCompleter("PythonEditor.Interpreter.History");
- interpreterAspect->setValue(exec.isEmpty() ? "python" : exec);
+
+ connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
+ interpreterAspect, &InterpreterAspect::updateInterpreters);
+
+ interpreterAspect->updateInterpreters(PythonSettings::interpreters());
+ interpreterAspect->setDefaultInterpreter(PythonSettings::defaultInterpreter());
auto scriptAspect = addAspect();
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
@@ -282,7 +374,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
setOutputFormatter();
setCommandLineGetter([this, interpreterAspect, argumentsAspect] {
- CommandLine cmd{interpreterAspect->value(), {mainScript()}};
+ CommandLine cmd{interpreterAspect->currentInterpreter().command, {mainScript()}};
cmd.addArgs(argumentsAspect->arguments(macroExpander()), CommandLine::Raw);
return cmd;
});
@@ -737,6 +829,8 @@ bool PythonPlugin::initialize(const QStringList &arguments, QString *errorMessag
ProjectManager::registerProjectType(PythonMimeType);
+ PythonSettings::init();
+
return true;
}
diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp
new file mode 100644
index 00000000000..2f7b7943811
--- /dev/null
+++ b/src/plugins/python/pythonsettings.cpp
@@ -0,0 +1,487 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pythonsettings.h"
+
+#include "pythonconstants.h"
+
+#include
+#include "coreplugin/icore.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Python {
+namespace Internal {
+
+using namespace Utils;
+
+class InterpreterDetailsWidget : public QWidget
+{
+public:
+ InterpreterDetailsWidget()
+ : m_name(new QLineEdit)
+ , m_executable(new Utils::PathChooser())
+ {
+ auto mainLayout = new QGridLayout();
+ mainLayout->addWidget(new QLabel(tr("Name:")), 0, 0);
+ mainLayout->addWidget(m_name, 0, 1);
+ mainLayout->addWidget(new QLabel(tr("Executable")), 1, 0);
+ mainLayout->addWidget(m_executable, 1, 1);
+ m_executable->setExpectedKind(Utils::PathChooser::ExistingCommand);
+ setLayout(mainLayout);
+ }
+
+ void updateInterpreter(const Interpreter &interpreter)
+ {
+ m_name->setText(interpreter.name);
+ m_executable->setPath(interpreter.command.toString());
+ m_currentId = interpreter.id;
+ }
+
+ Interpreter toInterpreter()
+ {
+ return {m_currentId, m_name->text(), FilePath::fromUserInput(m_executable->path())};
+ }
+ QLineEdit *m_name = nullptr;
+ PathChooser *m_executable = nullptr;
+ QString m_currentId;
+};
+
+
+class InterpreterOptionsWidget : public QWidget
+{
+public:
+ InterpreterOptionsWidget(const QList &interpreters,
+ const QString &defaultInterpreter);
+
+ void apply();
+
+private:
+ QTreeView m_view;
+ ListModel m_model;
+ InterpreterDetailsWidget *m_detailsWidget = nullptr;
+ QPushButton *m_deleteButton = nullptr;
+ QPushButton *m_makeDefaultButton = nullptr;
+ QString m_defaultId;
+
+ void currentChanged(const QModelIndex &index, const QModelIndex &previous);
+ void addItem();
+ void deleteItem();
+ void makeDefault();
+};
+
+InterpreterOptionsWidget::InterpreterOptionsWidget(const QList &interpreters, const QString &defaultInterpreter)
+ : m_detailsWidget(new InterpreterDetailsWidget())
+ , m_defaultId(defaultInterpreter)
+{
+ m_model.setDataAccessor([this](const Interpreter &interpreter, int, int role) -> QVariant {
+ if (role == Qt::DisplayRole)
+ return interpreter.name;
+ if (role == Qt::FontRole) {
+ QFont f = font();
+ f.setBold(interpreter.id == m_defaultId);
+ return f;
+ }
+ return {};
+ });
+
+ for (const Interpreter &interpreter : interpreters)
+ m_model.appendItem(interpreter);
+
+ auto mainLayout = new QVBoxLayout();
+ auto layout = new QHBoxLayout();
+ m_view.setModel(&m_model);
+ m_view.setHeaderHidden(true);
+ m_view.setSelectionMode(QAbstractItemView::SingleSelection);
+ m_view.setSelectionBehavior(QAbstractItemView::SelectItems);
+ connect(m_view.selectionModel(),
+ &QItemSelectionModel::currentChanged,
+ this,
+ &InterpreterOptionsWidget::currentChanged);
+ auto buttonLayout = new QVBoxLayout();
+ auto addButton = new QPushButton(InterpreterOptionsWidget::tr("&Add"));
+ connect(addButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::addItem);
+ m_deleteButton = new QPushButton(InterpreterOptionsWidget::tr("&Delete"));
+ m_deleteButton->setEnabled(false);
+ connect(m_deleteButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::deleteItem);
+ m_makeDefaultButton = new QPushButton(InterpreterOptionsWidget::tr("&Make Default"));
+ m_makeDefaultButton->setEnabled(false);
+ connect(m_makeDefaultButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::makeDefault);
+ mainLayout->addLayout(layout);
+ mainLayout->addWidget(m_detailsWidget);
+ m_detailsWidget->hide();
+ setLayout(mainLayout);
+ layout->addWidget(&m_view);
+ layout->addLayout(buttonLayout);
+ buttonLayout->addWidget(addButton);
+ buttonLayout->addWidget(m_deleteButton);
+ buttonLayout->addWidget(m_makeDefaultButton);
+ buttonLayout->addStretch(10);
+}
+
+void InterpreterOptionsWidget::apply()
+{
+ const QModelIndex &index = m_view.currentIndex();
+ if (index.isValid()) {
+ m_model.itemAt(index.row())->itemData = m_detailsWidget->toInterpreter();
+ emit m_model.dataChanged(index, index);
+ }
+
+ QList interpreters;
+ for (const ListItem *treeItem : m_model)
+ interpreters << treeItem->itemData;
+ PythonSettings::setInterpreter(interpreters, m_defaultId);
+}
+
+void InterpreterOptionsWidget::currentChanged(const QModelIndex &index, const QModelIndex &previous)
+{
+ if (previous.isValid()) {
+ m_model.itemAt(previous.row())->itemData = m_detailsWidget->toInterpreter();
+ emit m_model.dataChanged(previous, previous);
+ }
+ if (index.isValid()) {
+ m_detailsWidget->updateInterpreter(m_model.itemAt(index.row())->itemData);
+ m_detailsWidget->show();
+ } else {
+ m_detailsWidget->hide();
+ }
+ m_deleteButton->setEnabled(index.isValid());
+ m_makeDefaultButton->setEnabled(index.isValid());
+}
+
+void InterpreterOptionsWidget::addItem()
+{
+ const QModelIndex &index = m_model.indexForItem(
+ m_model.appendItem({QUuid::createUuid().toString(), "Python", FilePath()}));
+ QTC_ASSERT(index.isValid(), return);
+ m_view.setCurrentIndex(index);
+}
+
+void InterpreterOptionsWidget::deleteItem()
+{
+ const QModelIndex &index = m_view.currentIndex();
+ if (index.isValid())
+ m_model.destroyItem(m_model.itemAt(index.row()));
+}
+
+class InterpreterOptionsPage : public Core::IOptionsPage
+{
+ Q_OBJECT
+
+public:
+ InterpreterOptionsPage();
+
+ void setInterpreter(const QList &interpreters) { m_interpreters = interpreters; }
+ QList interpreters() const { return m_interpreters; }
+ void setDefaultInterpreter(const QString &defaultId)
+ { m_defaultInterpreterId = defaultId; }
+ Interpreter defaultInterpreter() const;
+
+ QWidget *widget() override;
+ void apply() override;
+ void finish() override;
+
+private:
+ QPointer m_widget;
+ QList m_interpreters;
+ QString m_defaultInterpreterId;
+};
+
+InterpreterOptionsPage::InterpreterOptionsPage()
+{
+ setId(Constants::C_PYTHONOPTIONS_PAGE_ID);
+ setDisplayName(tr("Interpreters"));
+ setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY);
+ setDisplayCategory(tr("Python"));
+ setCategoryIcon(Utils::Icon({{":/python/images/settingscategory_python.png",
+ Utils::Theme::PanelTextColorDark}}, Utils::Icon::Tint));
+}
+
+Interpreter InterpreterOptionsPage::defaultInterpreter() const
+{
+ if (m_defaultInterpreterId.isEmpty())
+ return {};
+ return Utils::findOrDefault(m_interpreters, [this](const Interpreter &interpreter) {
+ return interpreter.id == m_defaultInterpreterId;
+ });
+}
+
+QWidget *InterpreterOptionsPage::widget()
+{
+ if (!m_widget)
+ m_widget = new InterpreterOptionsWidget(m_interpreters, m_defaultInterpreterId);
+ return m_widget;
+}
+
+void InterpreterOptionsPage::apply()
+{
+ if (m_widget)
+ m_widget->apply();
+}
+
+void InterpreterOptionsPage::finish()
+{
+ delete m_widget;
+ m_widget = nullptr;
+}
+
+static bool alreadyRegistered(const QList &pythons, const FilePath &pythonExecutable)
+{
+ return Utils::anyOf(pythons, [pythonExecutable](const Interpreter &interpreter) {
+ return interpreter.command.toFileInfo().canonicalFilePath()
+ == pythonExecutable.toFileInfo().canonicalFilePath();
+ });
+}
+
+Interpreter interpreterForPythonExecutable(const FilePath &python,
+ const QString &defaultName,
+ bool windowedSuffix = false)
+{
+ SynchronousProcess pythonProcess;
+ pythonProcess.setProcessChannelMode(QProcess::MergedChannels);
+ SynchronousProcessResponse response = pythonProcess.runBlocking(
+ CommandLine(python, {"--version"}));
+ QString name;
+ if (response.result == SynchronousProcessResponse::Finished)
+ name = response.stdOut().trimmed();
+ if (name.isEmpty())
+ name = defaultName;
+ if (windowedSuffix)
+ name += " (Windowed)";
+ return Interpreter{QUuid::createUuid().toString(), name, python};
+}
+
+static InterpreterOptionsPage &interpreterOptionsPage()
+{
+ static InterpreterOptionsPage page;
+ return page;
+}
+
+void InterpreterOptionsWidget::makeDefault()
+{
+ const QModelIndex &index = m_view.currentIndex();
+ if (index.isValid()) {
+ QModelIndex defaultIndex;
+ if (auto *defaultItem = m_model.findItemByData(
+ [this](const Interpreter &interpreter) { return interpreter.id == m_defaultId; })) {
+ defaultIndex = m_model.indexForItem(defaultItem);
+ }
+ m_defaultId = m_model.itemAt(index.row())->itemData.id;
+ emit m_model.dataChanged(index, index, {Qt::FontRole});
+ if (defaultIndex.isValid())
+ emit m_model.dataChanged(defaultIndex, defaultIndex, {Qt::FontRole});
+ }
+}
+
+constexpr char settingsGroupKey[] = "Python";
+constexpr char interpreterKey[] = "Interpeter";
+constexpr char defaultKey[] = "DefaultInterpeter";
+
+struct SavedSettings
+{
+ QList pythons;
+ QString defaultId;
+};
+
+static SavedSettings fromSettings(QSettings *settings)
+{
+ QList pythons;
+ settings->beginGroup(settingsGroupKey);
+ const QVariantList interpreters = settings->value(interpreterKey).toList();
+ for (const QVariant &interpreterVar : interpreters) {
+ auto interpreterList = interpreterVar.toList();
+ if (interpreterList.size() != 3)
+ continue;
+ pythons << Interpreter{interpreterList.value(0).toString(),
+ interpreterList.value(1).toString(),
+ FilePath::fromVariant(interpreterList.value(2))};
+ }
+
+ const QString defaultId = settings->value(defaultKey).toString();
+
+ settings->endGroup();
+
+ return {pythons, defaultId};
+}
+
+static void toSettings(QSettings *settings, const SavedSettings &savedSettings)
+{
+ settings->beginGroup(settingsGroupKey);
+ const QVariantList interpretersVar
+ = Utils::transform(savedSettings.pythons, [](const Interpreter &interpreter) {
+ return QVariant({interpreter.id, interpreter.name, interpreter.command.toVariant()});
+ });
+ settings->setValue(interpreterKey, interpretersVar);
+ settings->setValue(defaultKey, savedSettings.defaultId);
+ settings->endGroup();
+}
+
+static void addPythonsFromRegistry(QList &pythons)
+{
+ QSettings pythonRegistry("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore",
+ QSettings::NativeFormat);
+ for (const QString &versionGroup : pythonRegistry.childGroups()) {
+ pythonRegistry.beginGroup(versionGroup);
+ QString name = pythonRegistry.value("DisplayName").toString();
+ QVariant regVal = pythonRegistry.value("InstallPath/ExecutablePath");
+ if (regVal.isValid()) {
+ const FilePath &executable = FilePath::fromUserInput(regVal.toString());
+ if (executable.exists() && !alreadyRegistered(pythons, executable)) {
+ pythons << Interpreter{QUuid::createUuid().toString(),
+ name,
+ FilePath::fromUserInput(regVal.toString())};
+ }
+ }
+ regVal = pythonRegistry.value("InstallPath/WindowedExecutablePath");
+ if (regVal.isValid()) {
+ const FilePath &executable = FilePath::fromUserInput(regVal.toString());
+ if (executable.exists() && !alreadyRegistered(pythons, executable)) {
+ pythons << Interpreter{QUuid::createUuid().toString(),
+ name + InterpreterOptionsPage::tr(" (Windowed)"),
+ FilePath::fromUserInput(regVal.toString())};
+ }
+ }
+ regVal = pythonRegistry.value("InstallPath/.");
+ if (regVal.isValid()) {
+ const FilePath &path = FilePath::fromUserInput(regVal.toString());
+ const FilePath &python = path.pathAppended(HostOsInfo::withExecutableSuffix("python"));
+ if (python.exists() && !alreadyRegistered(pythons, python))
+ pythons << interpreterForPythonExecutable(python, "Python " + versionGroup);
+ const FilePath &pythonw = path.pathAppended(
+ HostOsInfo::withExecutableSuffix("pythonw"));
+ if (pythonw.exists() && !alreadyRegistered(pythons, pythonw))
+ pythons << interpreterForPythonExecutable(pythonw, "Python " + versionGroup, true);
+ }
+ pythonRegistry.endGroup();
+ }
+}
+
+static void addPythonsFromPath(QList &pythons)
+{
+ const auto &env = Environment::systemEnvironment();
+
+ if (HostOsInfo::isWindowsHost()) {
+ for (const FilePath &executable : env.findAllInPath("python")) {
+ if (executable.exists() && !alreadyRegistered(pythons, executable))
+ pythons << interpreterForPythonExecutable(executable, "Python from Path");
+ }
+ for (const FilePath &executable : env.findAllInPath("pythonw")) {
+ if (executable.exists() && !alreadyRegistered(pythons, executable))
+ pythons << interpreterForPythonExecutable(executable, "Python from Path", true);
+ }
+ } else {
+ const QStringList filters = {"python",
+ "python[1-9].[0-9]",
+ "python[1-9].[1-9][0-9]",
+ "python[1-9]"};
+ for (const FilePath &path : env.path()) {
+ const QDir dir(path.toString());
+ for (const QFileInfo &fi : dir.entryInfoList(filters)) {
+ const FilePath executable = Utils::FilePath::fromFileInfo(fi);
+ if (executable.exists() && !alreadyRegistered(pythons, executable))
+ pythons << interpreterForPythonExecutable(executable, "Python from Path");
+ }
+ }
+ }
+}
+
+static QString idForPythonFromPath(QList pythons)
+{
+ const FilePath &pythonFromPath = Environment::systemEnvironment().searchInPath("python");
+ if (pythonFromPath.isEmpty())
+ return {};
+ const Interpreter &defaultInterpreter
+ = findOrDefault(pythons, [pythonFromPath](const Interpreter &interpreter) {
+ return interpreter.command == pythonFromPath;
+ });
+ return defaultInterpreter.id;
+}
+
+static PythonSettings *settingsInstance = nullptr;
+PythonSettings::PythonSettings() = default;
+
+void PythonSettings::init()
+{
+ QTC_ASSERT(!settingsInstance, return );
+ settingsInstance = new PythonSettings();
+
+ const SavedSettings &settings = fromSettings(Core::ICore::settings());
+ QList pythons = settings.pythons;
+
+ if (HostOsInfo::isWindowsHost())
+ addPythonsFromRegistry(pythons);
+ addPythonsFromPath(pythons);
+
+ const QString &defaultId = !settings.defaultId.isEmpty() ? settings.defaultId
+ : idForPythonFromPath(pythons);
+ setInterpreter(pythons, defaultId);
+}
+
+void PythonSettings::setInterpreter(const QList &interpreters, const QString &defaultId)
+{
+ interpreterOptionsPage().setInterpreter(interpreters);
+ interpreterOptionsPage().setDefaultInterpreter(defaultId);
+ toSettings(Core::ICore::settings(), {interpreters, defaultId});
+ if (QTC_GUARD(settingsInstance))
+ emit settingsInstance->interpretersChanged(interpreters, defaultId);
+}
+
+PythonSettings *PythonSettings::instance()
+{
+ QTC_CHECK(settingsInstance);
+ return settingsInstance;
+}
+
+QList PythonSettings::interpreters()
+{
+ return interpreterOptionsPage().interpreters();
+}
+
+Interpreter PythonSettings::defaultInterpreter()
+{
+ return interpreterOptionsPage().defaultInterpreter();
+}
+
+} // namespace Internal
+} // namespace Python
+
+#include "pythonsettings.moc"
diff --git a/src/plugins/python/pythonsettings.h b/src/plugins/python/pythonsettings.h
new file mode 100644
index 00000000000..583d4fb212f
--- /dev/null
+++ b/src/plugins/python/pythonsettings.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+#include
+
+namespace Python {
+namespace Internal {
+
+class Interpreter
+{
+public:
+ QString id;
+ QString name;
+ Utils::FilePath command;
+};
+
+class PythonSettings : public QObject
+{
+ Q_OBJECT
+public:
+ static void init();
+
+ static QList interpreters();
+ static Interpreter defaultInterpreter();
+ static void setInterpreter(const QList &interpreters, const QString &defaultId);
+ static PythonSettings *instance();
+
+signals:
+ void interpretersChanged(const QList &interpreters, const QString &defaultId);
+
+private:
+ PythonSettings();
+};
+
+} // namespace Internal
+} // namespace PythonEditor
diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg
index e39995f492c..14cddf04208 100644
--- a/src/tools/icons/qtcreatoricons.svg
+++ b/src/tools/icons/qtcreatoricons.svg
@@ -3217,6 +3217,51 @@
id="path6672"
inkscape:connector-curvature="0" />
+
+
+
+
+
+
+
+