Python: make interpreter configurable

Add settings page to setup python interpreter and use a combo box in the
run configuration to switch between those configured interpreter.

Change-Id: I51014b785ea822ee138d39c788149f1d22901e55
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
David Schulz
2019-07-05 09:40:31 +02:00
parent 1fb41647eb
commit a99e5af1da
11 changed files with 722 additions and 16 deletions

View File

@@ -1,11 +1,13 @@
add_qtc_plugin(Python add_qtc_plugin(Python
PLUGIN_DEPENDS Core QtSupport ProjectExplorer TextEditor PLUGIN_DEPENDS Core QtSupport ProjectExplorer TextEditor
SOURCES SOURCES
python.qrc
pythoneditor.cpp pythoneditor.h pythoneditor.cpp pythoneditor.h
pythonconstants.h pythonconstants.h
pythonplugin.cpp pythonplugin.h pythonplugin.cpp pythonplugin.h
pythonformattoken.h pythonformattoken.h
pythonhighlighter.cpp pythonhighlighter.h pythonhighlighter.cpp pythonhighlighter.h
pythonindenter.cpp pythonindenter.h pythonindenter.cpp pythonindenter.h
pythonsettings.cpp pythonsettings.h
pythonscanner.cpp pythonscanner.h pythonscanner.cpp pythonscanner.h
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

View File

@@ -11,10 +11,15 @@ HEADERS += \
pythonindenter.h \ pythonindenter.h \
pythonformattoken.h \ pythonformattoken.h \
pythonscanner.h \ pythonscanner.h \
pythonsettings.h
SOURCES += \ SOURCES += \
pythonplugin.cpp \ pythonplugin.cpp \
pythoneditor.cpp \ pythoneditor.cpp \
pythonhighlighter.cpp \ pythonhighlighter.cpp \
pythonindenter.cpp \ pythonindenter.cpp \
pythonscanner.cpp pythonscanner.cpp \
pythonsettings.cpp
RESOURCES += \
python.qrc

View File

@@ -14,6 +14,7 @@ QtcPlugin {
Group { Group {
name: "General" name: "General"
files: [ files: [
"python.qrc",
"pythoneditor.cpp", "pythoneditor.h", "pythoneditor.cpp", "pythoneditor.h",
"pythonconstants.h", "pythonconstants.h",
"pythonplugin.cpp", "pythonplugin.h", "pythonplugin.cpp", "pythonplugin.h",
@@ -21,6 +22,7 @@ QtcPlugin {
"pythonindenter.cpp", "pythonindenter.h", "pythonindenter.cpp", "pythonindenter.h",
"pythonformattoken.h", "pythonformattoken.h",
"pythonscanner.h", "pythonscanner.cpp", "pythonscanner.h", "pythonscanner.cpp",
"pythonsettings.cpp", "pythonsettings.h",
] ]
} }
} }

View File

@@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/python">
<file>images/settingscategory_python.png</file>
<file>images/settingscategory_python@2x.png</file>
</qresource>
</RCC>

View File

@@ -34,6 +34,9 @@ const char C_PYTHONEDITOR_ID[] = "PythonEditor.PythonEditor";
const char C_EDITOR_DISPLAY_NAME[] = const char C_EDITOR_DISPLAY_NAME[] =
QT_TRANSLATE_NOOP("OpenWith::Editors", "Python Editor"); 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 * MIME type
******************************************************************************/ ******************************************************************************/

View File

@@ -27,6 +27,7 @@
#include "pythoneditor.h" #include "pythoneditor.h"
#include "pythonconstants.h" #include "pythonconstants.h"
#include "pythonhighlighter.h" #include "pythonhighlighter.h"
#include "pythonsettings.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h> #include <coreplugin/coreconstants.h>
@@ -56,16 +57,19 @@
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QComboBox>
#include <QDir> #include <QDir>
#include <QRegExp> #include <QFormLayout>
#include <QRegularExpression> #include <QJsonArray>
#include <QRegularExpressionMatch>
#include <QTextCursor>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonParseError> #include <QJsonParseError>
#include <QJsonValue> #include <QJsonValue>
#include <QJsonArray> #include <QPushButton>
#include <QRegExp>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QTextCursor>
using namespace Core; using namespace Core;
using namespace ProjectExplorer; using namespace ProjectExplorer;
@@ -217,14 +221,103 @@ private:
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
class InterpreterAspect : public BaseStringAspect class InterpreterAspect : public ProjectConfigurationAspect
{ {
Q_OBJECT Q_OBJECT
public: public:
InterpreterAspect() = default; InterpreterAspect() = default;
Interpreter currentInterpreter() const;
void updateInterpreters(const QList<Interpreter> &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<Interpreter> m_interpreters;
QPointer<QComboBox> 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<Interpreter> &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 class MainScriptAspect : public BaseStringAspect
{ {
Q_OBJECT Q_OBJECT
@@ -251,7 +344,7 @@ private:
bool supportsDebugger() const { return true; } bool supportsDebugger() const { return true; }
QString mainScript() const { return aspect<MainScriptAspect>()->value(); } QString mainScript() const { return aspect<MainScriptAspect>()->value(); }
QString arguments() const { return aspect<ArgumentsAspect>()->arguments(macroExpander()); } QString arguments() const { return aspect<ArgumentsAspect>()->arguments(macroExpander()); }
QString interpreter() const { return aspect<InterpreterAspect>()->value(); } QString interpreter() const { return aspect<InterpreterAspect>()->currentInterpreter().command.toString(); }
void updateTargetInformation(); void updateTargetInformation();
}; };
@@ -259,15 +352,14 @@ private:
PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id) PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
: RunConfiguration(target, id) : RunConfiguration(target, id)
{ {
const Environment sysEnv = Environment::systemEnvironment();
const QString exec = sysEnv.searchInPath("python").toString();
auto interpreterAspect = addAspect<InterpreterAspect>(); auto interpreterAspect = addAspect<InterpreterAspect>();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter"); interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
interpreterAspect->setLabelText(tr("Interpreter:"));
interpreterAspect->setDisplayStyle(BaseStringAspect::PathChooserDisplay); connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
interpreterAspect->setHistoryCompleter("PythonEditor.Interpreter.History"); interpreterAspect, &InterpreterAspect::updateInterpreters);
interpreterAspect->setValue(exec.isEmpty() ? "python" : exec);
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
interpreterAspect->setDefaultInterpreter(PythonSettings::defaultInterpreter());
auto scriptAspect = addAspect<MainScriptAspect>(); auto scriptAspect = addAspect<MainScriptAspect>();
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script"); scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
@@ -282,7 +374,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
setOutputFormatter<PythonOutputFormatter>(); setOutputFormatter<PythonOutputFormatter>();
setCommandLineGetter([this, interpreterAspect, argumentsAspect] { setCommandLineGetter([this, interpreterAspect, argumentsAspect] {
CommandLine cmd{interpreterAspect->value(), {mainScript()}}; CommandLine cmd{interpreterAspect->currentInterpreter().command, {mainScript()}};
cmd.addArgs(argumentsAspect->arguments(macroExpander()), CommandLine::Raw); cmd.addArgs(argumentsAspect->arguments(macroExpander()), CommandLine::Raw);
return cmd; return cmd;
}); });
@@ -737,6 +829,8 @@ bool PythonPlugin::initialize(const QStringList &arguments, QString *errorMessag
ProjectManager::registerProjectType<PythonProject>(PythonMimeType); ProjectManager::registerProjectType<PythonProject>(PythonMimeType);
PythonSettings::init();
return true; return true;
} }

View File

@@ -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 <coreplugin/dialogs/ioptionspage.h>
#include "coreplugin/icore.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/detailswidget.h>
#include <utils/environment.h>
#include <utils/listmodel.h>
#include <utils/pathchooser.h>
#include <utils/synchronousprocess.h>
#include <utils/treemodel.h>
#include <QDir>
#include <QLabel>
#include <QPushButton>
#include <QPointer>
#include <QSettings>
#include <QStackedWidget>
#include <QTreeView>
#include <QVBoxLayout>
#include <QWidget>
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<Interpreter> &interpreters,
const QString &defaultInterpreter);
void apply();
private:
QTreeView m_view;
ListModel<Interpreter> 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<Interpreter> &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<Interpreter> interpreters;
for (const ListItem<Interpreter> *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<Interpreter> &interpreters) { m_interpreters = interpreters; }
QList<Interpreter> 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<InterpreterOptionsWidget> m_widget;
QList<Interpreter> 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<Interpreter> &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<Interpreter> pythons;
QString defaultId;
};
static SavedSettings fromSettings(QSettings *settings)
{
QList<Interpreter> 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<Interpreter> &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<Interpreter> &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<Interpreter> 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<Interpreter> 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<Interpreter> &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<Interpreter> PythonSettings::interpreters()
{
return interpreterOptionsPage().interpreters();
}
Interpreter PythonSettings::defaultInterpreter()
{
return interpreterOptionsPage().defaultInterpreter();
}
} // namespace Internal
} // namespace Python
#include "pythonsettings.moc"

View File

@@ -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 <utils/fileutils.h>
#include <QUuid>
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<Interpreter> interpreters();
static Interpreter defaultInterpreter();
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
static PythonSettings *instance();
signals:
void interpretersChanged(const QList<Interpreter> &interpreters, const QString &defaultId);
private:
PythonSettings();
};
} // namespace Internal
} // namespace PythonEditor

View File

@@ -3217,6 +3217,51 @@
id="path6672" id="path6672"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
</g> </g>
<g
transform="translate(-213,-84)"
id="src/plugins/python/images/settingscategory_python">
<use
style="display:inline"
transform="translate(1128,148)"
height="100%"
width="100%"
id="use2493"
xlink:href="#backgroundRect_24"
y="0"
x="0" />
<path
sodipodi:nodetypes="ccccccsssscc"
inkscape:connector-curvature="0"
id="path1631-9"
d="m 1112,567 v 1 h 4 v 1 h -4 c -5.3,0 -5.3,7 0,7 v -1.5 c 0,-1.5 1.5,-2.5 2.5,-2.5 h 2 c 1.8137,0 2.5,-0.75 2.5,-2.5 V 567 c 0,-2.66 -7,-2.66 -7,0 z"
style="stroke-width:0" />
<use
x="0"
y="0"
xlink:href="#path1631-9"
id="use2537"
transform="rotate(-180,1116,572.5)"
width="100%"
height="100%" />
<path
style="fill:#ffffff"
d="m 1121,570.75 c 0,1.75 -1.5,3.25 -3.5,3.25 h -2.5 c -0.66,0 -1,0.33 -1,1 v 3 c 0,0 -0.031,1 2.3654,1 1.7918,0 2.6346,0.21875 2.6346,-0.99998 h -4 v -3 h 5 c 4,0 3.5,-4.75 1,-4.75 z"
id="path2539"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sscccscccccs" />
<circle
style="fill:#ffffff"
id="path2541"
cx="1113.5"
cy="566.5"
r="0.625" />
<circle
style="fill:#000000"
id="path2541-4"
cx="1117.5"
cy="578.5"
r="0.5" />
</g>
<g <g
id="src/plugins/autotest/images/settingscategory_autotest" id="src/plugins/autotest/images/settingscategory_autotest"
transform="translate(-220,-84)"> transform="translate(-220,-84)">

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 379 KiB