Python: Use model in aspect combo box

For icons and tooltips consistent with the settings page.

Task-number: QTCREATORBUG-31574
Change-Id: Ib567f43e0b29fa058770eefb4b16e8610fd28adf
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2024-10-02 16:57:44 +02:00
parent 5a09ce5d53
commit 79774519cc
3 changed files with 127 additions and 63 deletions

View File

@@ -20,8 +20,49 @@ using namespace ProjectExplorer;
using namespace Utils;
namespace Python {
namespace Internal {
using namespace Internal;
class PythonAspectSortModel : public SortModel
{
public:
PythonAspectSortModel(QObject *parent) : SortModel(parent) {}
void reset()
{
beginResetModel();
if (QAbstractItemModel * const model = sourceModel())
model->deleteLater();
ListModel<Interpreter> * const model = createInterpreterModel(this);
model->setAllData(model->allData() << Interpreter("none", {}, {}));
setSourceModel(model);
endResetModel();
sort(0);
}
private:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
{
const auto source = static_cast<ListModel<Interpreter> *>(sourceModel());
const auto item1 = static_cast<ListItem<Interpreter> *>(source->itemForIndex(source_left));
const auto item2 = static_cast<ListItem<Interpreter> *>(source->itemForIndex(source_right));
QTC_ASSERT(item1 && item2, return false);
// Criterion 1: "None" comes last
if (item1->itemData.id == "none")
return false;
if (item2->itemData.id == "none")
return true;
// Criterion 2: Valid interpreters come before invalid ones.
const bool item1Valid = PythonSettings::interpreterIsValid(item1->itemData);
const bool item2Valid = PythonSettings::interpreterIsValid(item2->itemData);
if (item1Valid != item2Valid)
return item1Valid;
// Criterion 3: Name.
return SortModel::lessThan(source_left, source_right);
}
};
class PythonKitAspectImpl final : public KitAspect
{
@@ -33,6 +74,7 @@ public:
m_comboBox = createSubWidget<QComboBox>();
m_comboBox->setSizePolicy(QSizePolicy::Ignored, m_comboBox->sizePolicy().verticalPolicy());
m_comboBox->setModel(new PythonAspectSortModel(this));
refresh();
m_comboBox->setToolTip(kitInfo->description());
@@ -56,19 +98,18 @@ public:
void refresh() override
{
const GuardLocker locker(m_ignoreChanges);
m_comboBox->clear();
m_comboBox->addItem(Tr::tr("None"), QString());
for (const Interpreter &interpreter : PythonSettings::interpreters())
m_comboBox->addItem(interpreter.name, interpreter.id);
static_cast<PythonAspectSortModel *>(m_comboBox->model())->reset();
updateComboBox(PythonKitAspect::python(kit()));
emit changed(); // we need to emit changed here to update changes in the macro expander
}
void updateComboBox(const std::optional<Interpreter> &python)
{
const int index = python ? std::max(m_comboBox->findData(python->id), 0) : 0;
int index = m_comboBox->count() - 1;
if (python) {
if (const int idx = m_comboBox->findData(python->id); idx != -1)
index = idx;
}
m_comboBox->setCurrentIndex(index);
}
@@ -175,6 +216,10 @@ public:
}
};
} // Internal
using namespace Internal;
std::optional<Interpreter> PythonKitAspect::python(const Kit *kit)
{
QTC_ASSERT(kit, return {});

View File

@@ -144,7 +144,7 @@ public:
private:
QTreeView *m_view = nullptr;
ListModel<Interpreter> m_model;
ListModel<Interpreter> * const m_model;
InterpreterDetailsWidget *m_detailsWidget = nullptr;
QPushButton *m_deleteButton = nullptr;
QPushButton *m_makeDefaultButton = nullptr;
@@ -164,42 +164,10 @@ private:
};
InterpreterOptionsWidget::InterpreterOptionsWidget()
: m_detailsWidget(new InterpreterDetailsWidget(this))
: m_model(createInterpreterModel(this))
, m_detailsWidget(new InterpreterDetailsWidget(this))
, m_defaultId(PythonSettings::defaultInterpreter().id)
{
m_model.setDataAccessor([this](const Interpreter &interpreter, int column, int role) -> QVariant {
switch (role) {
case Qt::DisplayRole:
return interpreter.name;
case Qt::FontRole: {
QFont f = font();
f.setBold(interpreter.id == m_defaultId);
return f;
}
case Qt::ToolTipRole:
if (interpreter.command.needsDevice())
break;
if (interpreter.command.isEmpty())
return Tr::tr("Executable is empty.");
if (!interpreter.command.exists())
return Tr::tr("\"%1\" does not exist.").arg(interpreter.command.toUserOutput());
if (!interpreter.command.isExecutableFile())
return Tr::tr("\"%1\" is not an executable file.")
.arg(interpreter.command.toUserOutput());
break;
case Qt::DecorationRole:
if (interpreter.command.needsDevice())
break;
if (column == 0 && !interpreter.command.isExecutableFile())
return Utils::Icons::CRITICAL.icon();
break;
default:
break;
}
return {};
});
m_model.setAllData(PythonSettings::interpreters());
auto addButton = new QPushButton(Tr::tr("&Add"), this);
m_deleteButton = new QPushButton(Tr::tr("&Delete"), this);
@@ -232,7 +200,7 @@ InterpreterOptionsWidget::InterpreterOptionsWidget()
m_detailsWidget->hide();
m_view->setModel(&m_model);
m_view->setModel(m_model);
m_view->setHeaderHidden(true);
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_view->setSelectionBehavior(QAbstractItemView::SelectItems);
@@ -256,35 +224,35 @@ void InterpreterOptionsWidget::apply()
void InterpreterOptionsWidget::addInterpreter(const Interpreter &interpreter)
{
m_model.appendItem(interpreter);
m_model->appendItem(interpreter);
}
void InterpreterOptionsWidget::removeInterpreterFrom(const QString &detectionSource)
{
m_model.destroyItems(Utils::equal(&Interpreter::detectionSource, detectionSource));
m_model->destroyItems(Utils::equal(&Interpreter::detectionSource, detectionSource));
}
QList<Interpreter> InterpreterOptionsWidget::interpreters() const
{
QList<Interpreter> interpreters;
for (const TreeItem *treeItem : m_model)
for (const TreeItem *treeItem : *m_model)
interpreters << static_cast<const ListItem<Interpreter> *>(treeItem)->itemData;
return interpreters;
}
QList<Interpreter> InterpreterOptionsWidget::interpreterFrom(const QString &detectionSource) const
{
return m_model.allData(Utils::equal(&Interpreter::detectionSource, detectionSource));
return m_model->allData(Utils::equal(&Interpreter::detectionSource, detectionSource));
}
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);
m_model->itemAt(previous.row())->itemData = m_detailsWidget->toInterpreter();
emit m_model->dataChanged(previous, previous);
}
if (index.isValid()) {
const Interpreter interpreter = m_model.itemAt(index.row())->itemData;
const Interpreter interpreter = m_model->itemAt(index.row())->itemData;
m_detailsWidget->updateInterpreter(interpreter);
m_detailsWidget->show();
updateGenerateKitButton(interpreter);
@@ -301,8 +269,8 @@ void InterpreterOptionsWidget::detailsChanged()
const QModelIndex &index = m_view->currentIndex();
if (index.isValid()) {
const Interpreter interpreter = m_detailsWidget->toInterpreter();
m_model.itemAt(index.row())->itemData = interpreter;
emit m_model.dataChanged(index, index);
m_model->itemAt(index.row())->itemData = interpreter;
emit m_model->dataChanged(index, index);
updateGenerateKitButton(interpreter);
}
updateCleanButton();
@@ -310,7 +278,7 @@ void InterpreterOptionsWidget::detailsChanged()
void InterpreterOptionsWidget::updateCleanButton()
{
m_cleanButton->setEnabled(Utils::anyOf(m_model.allData(), [](const Interpreter &interpreter) {
m_cleanButton->setEnabled(Utils::anyOf(m_model->allData(), [](const Interpreter &interpreter) {
return !interpreter.command.isExecutableFile();
}));
}
@@ -324,8 +292,8 @@ void InterpreterOptionsWidget::updateGenerateKitButton(const Interpreter &interp
void InterpreterOptionsWidget::addItem()
{
const QModelIndex &index = m_model.indexForItem(
m_model.appendItem({QUuid::createUuid().toString(), QString("Python"), FilePath(), false}));
const QModelIndex &index = m_model->indexForItem(m_model->appendItem(
{QUuid::createUuid().toString(), QString("Python"), FilePath(), false}));
QTC_ASSERT(index.isValid(), return);
m_view->setCurrentIndex(index);
updateCleanButton();
@@ -335,7 +303,7 @@ void InterpreterOptionsWidget::deleteItem()
{
const QModelIndex &index = m_view->currentIndex();
if (index.isValid())
m_model.destroyItem(m_model.itemAt(index.row()));
m_model->destroyItem(m_model->itemAt(index.row()));
updateCleanButton();
}
@@ -564,13 +532,13 @@ void InterpreterOptionsWidget::makeDefault()
{
const QModelIndex &index = m_view->currentIndex();
if (index.isValid()) {
QModelIndex defaultIndex = m_model.findIndex([this](const Interpreter &interpreter) {
QModelIndex defaultIndex = m_model->findIndex([this](const Interpreter &interpreter) {
return interpreter.id == m_defaultId;
});
m_defaultId = m_model.itemAt(index.row())->itemData.id;
emit m_model.dataChanged(index, index, {Qt::FontRole});
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});
emit m_model->dataChanged(defaultIndex, defaultIndex, {Qt::FontRole});
}
}
@@ -578,13 +546,13 @@ void InterpreterOptionsWidget::generateKit()
{
const QModelIndex &index = m_view->currentIndex();
if (index.isValid())
PythonSettings::addKitsForInterpreter(m_model.itemAt(index.row())->itemData, true);
PythonSettings::addKitsForInterpreter(m_model->itemAt(index.row())->itemData, true);
m_generateKitButton->setEnabled(false);
}
void InterpreterOptionsWidget::cleanUp()
{
m_model.destroyItems(
m_model->destroyItems(
[](const Interpreter &interpreter) { return !interpreter.command.isExecutableFile(); });
updateCleanButton();
}
@@ -846,6 +814,11 @@ void PythonSettings::removeKitsForInterpreter(const Interpreter &interpreter)
KitManager::deregisterKit(k);
}
bool PythonSettings::interpreterIsValid(const Interpreter &interpreter)
{
return !interpreter.command.needsDevice() || interpreter.command.isExecutableFile();
}
void PythonSettings::setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId)
{
if (defaultId == settingsInstance->m_defaultInterpreterId
@@ -1203,6 +1176,49 @@ void setupPythonSettings(QObject *guard)
settingsInstance->setParent(guard);
}
Utils::ListModel<ProjectExplorer::Interpreter> *createInterpreterModel(QObject *parent)
{
const auto model = new ListModel<Interpreter>(parent);
model->setDataAccessor([](const Interpreter &interpreter, int column, int role) -> QVariant {
if (interpreter.id == "none") {
if (role == Qt::DisplayRole)
return Tr::tr("none");
return {};
}
switch (role) {
case Qt::DisplayRole:
return interpreter.name;
case Qt::FontRole: {
QFont f;
f.setBold(interpreter.id == PythonSettings::defaultInterpreter().id);
return f;
}
case Qt::ToolTipRole:
if (interpreter.command.needsDevice())
break;
if (interpreter.command.isEmpty())
return Tr::tr("Executable is empty.");
if (!interpreter.command.exists())
return Tr::tr("\"%1\" does not exist.").arg(interpreter.command.toUserOutput());
if (!interpreter.command.isExecutableFile())
return Tr::tr("\"%1\" is not an executable file.")
.arg(interpreter.command.toUserOutput());
break;
case Qt::DecorationRole:
if (column == 0 && !PythonSettings::interpreterIsValid(interpreter))
return Utils::Icons::CRITICAL.icon();
break;
case Qt::UserRole:
return interpreter.id;
default:
break;
}
return {};
});
model->setAllData(PythonSettings::interpreters());
return model;
}
} // Python::Internal
#include "pythonsettings.moc"

View File

@@ -8,6 +8,7 @@
#include <solutions/tasking/tasktreerunner.h>
#include <utils/filepath.h>
#include <utils/listmodel.h>
namespace Python::Internal {
@@ -45,6 +46,7 @@ public:
static QList<Interpreter> detectPythonVenvs(const Utils::FilePath &path);
static void addKitsForInterpreter(const Interpreter &interpreter, bool force);
static void removeKitsForInterpreter(const Interpreter &interpreter);
static bool interpreterIsValid(const Interpreter &interpreter);
signals:
void interpretersChanged(const QList<Interpreter> &interpreters, const QString &defaultId);
@@ -77,5 +79,6 @@ private:
};
void setupPythonSettings(QObject *guard);
Utils::ListModel<ProjectExplorer::Interpreter> *createInterpreterModel(QObject *parent);
} // Python::Internal