forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
)
|
)
|
||||||
|
BIN
src/plugins/python/images/settingscategory_python.png
Normal file
BIN
src/plugins/python/images/settingscategory_python.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 227 B |
BIN
src/plugins/python/images/settingscategory_python@2x.png
Normal file
BIN
src/plugins/python/images/settingscategory_python@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 446 B |
@@ -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
|
||||||
|
@@ -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",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
src/plugins/python/python.qrc
Normal file
6
src/plugins/python/python.qrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/python">
|
||||||
|
<file>images/settingscategory_python.png</file>
|
||||||
|
<file>images/settingscategory_python@2x.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
@@ -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
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
487
src/plugins/python/pythonsettings.cpp
Normal file
487
src/plugins/python/pythonsettings.cpp
Normal 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"
|
62
src/plugins/python/pythonsettings.h
Normal file
62
src/plugins/python/pythonsettings.h
Normal 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
|
@@ -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 |
Reference in New Issue
Block a user