From 79774519ccaa19fbee58c8a191281f286eb83be2 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 2 Oct 2024 16:57:44 +0200 Subject: [PATCH] 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 --- src/plugins/python/pythonkitaspect.cpp | 61 ++++++++++-- src/plugins/python/pythonsettings.cpp | 126 ++++++++++++++----------- src/plugins/python/pythonsettings.h | 3 + 3 files changed, 127 insertions(+), 63 deletions(-) diff --git a/src/plugins/python/pythonkitaspect.cpp b/src/plugins/python/pythonkitaspect.cpp index 8bdc557d946..c555caff657 100644 --- a/src/plugins/python/pythonkitaspect.cpp +++ b/src/plugins/python/pythonkitaspect.cpp @@ -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 * 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 *>(sourceModel()); + const auto item1 = static_cast *>(source->itemForIndex(source_left)); + const auto item2 = static_cast *>(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(); 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(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 &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 PythonKitAspect::python(const Kit *kit) { QTC_ASSERT(kit, return {}); diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index 6c8fff0e615..ab8650be0b3 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -144,7 +144,7 @@ public: private: QTreeView *m_view = nullptr; - ListModel m_model; + ListModel * 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 InterpreterOptionsWidget::interpreters() const { QList interpreters; - for (const TreeItem *treeItem : m_model) + for (const TreeItem *treeItem : *m_model) interpreters << static_cast *>(treeItem)->itemData; return interpreters; } QList 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 &interpreters, const QString &defaultId) { if (defaultId == settingsInstance->m_defaultInterpreterId @@ -1203,6 +1176,49 @@ void setupPythonSettings(QObject *guard) settingsInstance->setParent(guard); } +Utils::ListModel *createInterpreterModel(QObject *parent) +{ + const auto model = new ListModel(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" diff --git a/src/plugins/python/pythonsettings.h b/src/plugins/python/pythonsettings.h index 6419335a32f..ff6584e6fd9 100644 --- a/src/plugins/python/pythonsettings.h +++ b/src/plugins/python/pythonsettings.h @@ -8,6 +8,7 @@ #include #include +#include namespace Python::Internal { @@ -45,6 +46,7 @@ public: static QList 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 &interpreters, const QString &defaultId); @@ -77,5 +79,6 @@ private: }; void setupPythonSettings(QObject *guard); +Utils::ListModel *createInterpreterModel(QObject *parent); } // Python::Internal