diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp index 375176f4939..44cc4daf815 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp @@ -139,21 +139,23 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) connect(tree, &Utils::TreeView::activated, tree, [tree](const QModelIndex &idx) { tree->edit(idx); }); m_configView = tree; + m_configFilterModel->setSourceModel(m_configModel); - m_configFilterModel->setFilterKeyColumn(2); - m_configFilterModel->setFilterFixedString(QLatin1String("0")); + m_configFilterModel->setFilterKeyColumn(0); + m_configFilterModel->setFilterRole(ConfigModel::ItemIsAdvancedRole); + m_configFilterModel->setFilterFixedString("0"); + m_configTextFilterModel->setSourceModel(m_configFilterModel); m_configTextFilterModel->setFilterKeyColumn(-1); m_configTextFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_configView->setModel(m_configTextFilterModel); m_configView->setMinimumHeight(300); - m_configView->setRootIsDecorated(false); m_configView->setUniformRowHeights(true); auto stretcher = new Utils::HeaderViewStretcher(m_configView->header(), 1); m_configView->setSelectionMode(QAbstractItemView::SingleSelection); m_configView->setSelectionBehavior(QAbstractItemView::SelectItems); m_configView->setFrameShape(QFrame::NoFrame); - m_configView->hideColumn(2); // Hide isAdvanced column m_configView->setItemDelegate(new ConfigModelItemDelegate(m_configView)); QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(m_configView, Core::ItemViewFind::LightColored); findWrapper->setFrameStyle(QFrame::StyledPanel); @@ -311,8 +313,10 @@ void CMakeBuildSettingsWidget::updateButtonState() void CMakeBuildSettingsWidget::updateAdvancedCheckBox() { - // Switch between Qt::DisplayRole (everything is "0") and Qt::EditRole (advanced is "1"). - m_configFilterModel->setFilterRole(m_showAdvancedCheckBox->isChecked() ? Qt::EditRole : Qt::DisplayRole); + if (m_showAdvancedCheckBox->isChecked()) + m_configTextFilterModel->setSourceModel(m_configModel); + else + m_configTextFilterModel->setSourceModel(m_configFilterModel); } void CMakeBuildSettingsWidget::updateFromKit() diff --git a/src/plugins/cmakeprojectmanager/configmodel.cpp b/src/plugins/cmakeprojectmanager/configmodel.cpp index 7336a2a7a2b..e828aad16fb 100644 --- a/src/plugins/cmakeprojectmanager/configmodel.cpp +++ b/src/plugins/cmakeprojectmanager/configmodel.cpp @@ -25,12 +25,14 @@ #include "configmodel.h" +#include #include #include #include #include #include +#include namespace CMakeProjectManager { @@ -41,186 +43,27 @@ static bool isTrue(const QString &value) || lower == QStringLiteral("1") || lower == QStringLiteral("yes"); } -ConfigModel::ConfigModel(QObject *parent) : QAbstractTableModel(parent) -{ } - -int ConfigModel::rowCount(const QModelIndex &parent) const +ConfigModel::ConfigModel(QObject *parent) : Utils::TreeModel<>(parent) { - return parent.isValid() ? 0 : m_configuration.count(); + setHeader({tr("Key"), tr("Value")}); } -int ConfigModel::columnCount(const QModelIndex &parent) const +QVariant ConfigModel::data(const QModelIndex &idx, int role) const { - return parent.isValid() ? 0 : 3; -} - -Qt::ItemFlags ConfigModel::flags(const QModelIndex &index) const -{ - QTC_ASSERT(index.model() == this, return Qt::NoItemFlags); - QTC_ASSERT(index.isValid(), return Qt::NoItemFlags); - QTC_ASSERT(index.column() >= 0 && index.column() < columnCount(QModelIndex()), return Qt::NoItemFlags); - QTC_ASSERT(index.row() >= 0 && index.row() < rowCount(QModelIndex()), return Qt::NoItemFlags); - - const InternalDataItem &item = itemAtRow(index.row()); - - if (index.column() == 1) { - if (item.type == DataItem::BOOLEAN) - return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; - else - return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; - } else { - Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; - if (item.isUserNew) - return flags |= Qt::ItemIsEditable; - return flags; - } -} - -QVariant ConfigModel::data(const QModelIndex &index, int role) const -{ - QTC_ASSERT(index.model() == this, return QVariant()); - QTC_ASSERT(index.isValid(), return QVariant()); - QTC_ASSERT(index.row() >= 0 && index.row() < rowCount(QModelIndex()), return QVariant()); - QTC_ASSERT(index.column() >= 0 && index.column() < columnCount(QModelIndex()), return QVariant()); - - const InternalDataItem &item = m_configuration[index.row()]; - - if (index.column() < 2) { - switch (role) { - case ItemTypeRole: - return item.type; - case ItemValuesRole: - return item.values; - } - } - - switch (index.column()) { - case 0: - switch (role) { - case Qt::DisplayRole: - return item.key.isEmpty() ? tr("") : item.key; - case Qt::EditRole: - return item.key; - case Qt::ToolTipRole: - return item.toolTip(); - case Qt::FontRole: { - QFont font; - font.setItalic(item.isCMakeChanged); - font.setBold(item.isUserNew); - font.setStrikeOut(!item.inCMakeCache && !item.isUserNew); - return font; - } - default: - return QVariant(); - } - case 1: { - const QString value = item.currentValue(); - const QString kitValue = m_kitConfiguartion.value(item.key); - - switch (role) { - case Qt::CheckStateRole: - return (item.type == DataItem::BOOLEAN) - ? QVariant(isTrue(value) ? Qt::Checked : Qt::Unchecked) : QVariant(); - case Qt::DisplayRole: - return value; - case Qt::EditRole: - return (item.type == DataItem::BOOLEAN) ? QVariant(isTrue(value)) : QVariant(value); - case Qt::FontRole: { - QFont font; - font.setBold(item.isUserChanged || item.isUserNew); - font.setItalic(item.isCMakeChanged); - return font; - } - case Qt::ForegroundRole: - return Utils::creatorTheme()->color((!kitValue.isNull() && kitValue != value) - ? Utils::Theme::TextColorHighlight : Utils::Theme::TextColorNormal); - case Qt::ToolTipRole: { - QString tooltip = item.toolTip(); - const QString kitValue = m_kitConfiguartion.value(item.key); - if (!kitValue.isNull()) { - if (!tooltip.isEmpty()) - tooltip.append("
"); - tooltip.append(tr("Kit value: %1").arg(kitValue)); - } - return tooltip; - } - default: - return QVariant(); - } - } - case 2: - switch (role) { - case Qt::EditRole: - return "0"; - case Qt::DisplayRole: - return QString::fromLatin1(item.isAdvanced ? "1" : "0"); - case Qt::CheckStateRole: - return item.isAdvanced ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool ConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - QTC_ASSERT(index.model() == this, return false); - QTC_ASSERT(index.isValid(), return false); - QTC_ASSERT(index.row() >= 0 && index.row() < rowCount(QModelIndex()), return false); - QTC_ASSERT(index.column() >= 0 && index.column() < 2, return false); - - QString newValue = value.toString(); - if (role == Qt::CheckStateRole) { - if (index.column() != 1) + // Hide/show groups according to "isAdvanced" setting: + Utils::TreeItem *item = static_cast(idx.internalPointer()); + if (role == ItemIsAdvancedRole && item->childCount() > 0) { + const bool hasNormalChildren = item->findAnyChild([](const Utils::TreeItem *ti) { + if (auto cmti = dynamic_cast(ti)) + return !cmti->dataItem->isAdvanced; return false; - newValue = QString::fromLatin1(value.toInt() == 0 ? "OFF" : "ON"); - } else if (role != Qt::EditRole) { - return false; - } - - InternalDataItem &item = itemAtRow(index.row()); - switch (index.column()) { - case 0: - if (!item.key.isEmpty() && !item.isUserNew) - return false; - item.key = newValue; - item.isUserNew = true; - item.isUserChanged = false; - emit dataChanged(index, index); - return true; - case 1: - if (item.value == newValue) { - item.newValue.clear(); - item.isUserChanged = false; - } else { - item.newValue = newValue; - item.isUserChanged = true; - } - emit dataChanged(index, index); - return true; - case 2: - default: - return false; + }) != nullptr; + return hasNormalChildren ? "0" : "1"; } + return Utils::TreeModel<>::data(idx, role); } -QVariant ConfigModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Vertical || role != Qt::DisplayRole) - return QVariant(); - switch (section) { - case 0: - return tr("Setting"); - case 1: - return tr("Value"); - case 2: - return tr("Advanced"); - default: - return QVariant(); - } -} +ConfigModel::~ConfigModel() = default; void ConfigModel::appendConfiguration(const QString &key, const QString &value, @@ -238,70 +81,33 @@ void ConfigModel::appendConfiguration(const QString &key, InternalDataItem internalItem(item); internalItem.isUserNew = true; - beginResetModel(); + if (m_kitConfiguration.contains(key)) + internalItem.kitValue = m_kitConfiguration.value(key); + m_configuration.append(internalItem); - endResetModel(); + setConfiguration(m_configuration); } -void ConfigModel::setConfiguration(const QList &config) +void ConfigModel::setConfiguration(const QList &config) { - QList tmp = config; - Utils::sort(tmp, - [](const ConfigModel::DataItem &i, const ConfigModel::DataItem &j) { - return i.key < j.key; - }); - auto newIt = tmp.constBegin(); - auto newEndIt = tmp.constEnd(); - auto oldIt = m_configuration.constBegin(); - auto oldEndIt = m_configuration.constEnd(); - - QList result; - while (newIt != newEndIt && oldIt != oldEndIt) { - if (newIt->isHidden) { - ++newIt; - } else if (newIt->key < oldIt->key) { - // Add new entry: - result << InternalDataItem(*newIt); - ++newIt; - } else if (newIt->key > oldIt->key) { - // Keep old user settings, but skip other entries: - if (oldIt->isUserChanged || oldIt->isUserNew) - result << InternalDataItem(*oldIt); - ++oldIt; - } else { - // merge old/new entry: - InternalDataItem item(*newIt); - item.newValue = (newIt->value != oldIt->newValue) ? oldIt->newValue : QString(); - item.isCMakeChanged = (oldIt->value != newIt->value); - item.isUserChanged = !item.newValue.isEmpty() && (item.newValue != item.value); - result << item; - ++newIt; - ++oldIt; - } - } - - // Add remaining new entries: - for (; newIt != newEndIt; ++newIt) { - if (newIt->isHidden) - continue; - result << InternalDataItem(*newIt); - } - - beginResetModel(); - m_configuration = result; - endResetModel(); + setConfiguration(Utils::transform(config, [](const DataItem &di) { return InternalDataItem(di); })); } void ConfigModel::setKitConfiguration(const QHash &kitConfig) { - m_kitConfiguartion = kitConfig; + m_kitConfiguration = kitConfig; + + for (InternalDataItem &i : m_configuration) { + if (m_kitConfiguration.contains(i.key)) { + i.kitValue = m_kitConfiguration.value(i.key); + } + } + setConfiguration(m_configuration); } void ConfigModel::flush() { - beginResetModel(); - m_configuration.clear(); - endResetModel(); + setConfiguration(QList()); } void ConfigModel::resetAllChanges() @@ -310,14 +116,12 @@ void ConfigModel::resetAllChanges() = Utils::filtered(m_configuration, [](const InternalDataItem &i) { return !i.isUserNew; }); - beginResetModel(); - m_configuration = Utils::transform(tmp, [](const InternalDataItem &i) -> InternalDataItem { + setConfiguration(Utils::transform(tmp, [](const InternalDataItem &i) { InternalDataItem ni(i); ni.newValue.clear(); ni.isUserChanged = false; return ni; - }); - endResetModel(); + })); } bool ConfigModel::hasChanges() const @@ -344,16 +148,101 @@ QList ConfigModel::configurationChanges() const }); } -ConfigModel::InternalDataItem &ConfigModel::itemAtRow(int row) +void ConfigModel::setConfiguration(const QList &config) { - QTC_CHECK(row >= 0); - return m_configuration[row]; + QList tmp = config; + Utils::sort(tmp, + [](const ConfigModel::InternalDataItem &i, const ConfigModel::InternalDataItem &j) { + return i.key < j.key; + }); + + auto newIt = tmp.constBegin(); + auto newEndIt = tmp.constEnd(); + auto oldIt = m_configuration.constBegin(); + auto oldEndIt = m_configuration.constEnd(); + + QList result; + while (newIt != newEndIt && oldIt != oldEndIt) { + if (newIt->isHidden) { + ++newIt; + } else if (newIt->key < oldIt->key) { + // Add new entry: + result << *newIt; + ++newIt; + } else if (newIt->key > oldIt->key) { + // Keep old user settings, but skip other entries: + if (oldIt->isUserChanged || oldIt->isUserNew) + result << InternalDataItem(*oldIt); + ++oldIt; + } else { + // merge old/new entry: + InternalDataItem item(*newIt); + item.newValue = (newIt->value != oldIt->newValue) ? oldIt->newValue : QString(); + item.isCMakeChanged = (oldIt->value != newIt->value); + item.isUserChanged = !item.newValue.isEmpty() && (item.newValue != item.value); + result << item; + ++newIt; + ++oldIt; + } + } + + // Add remaining new entries: + for (; newIt != newEndIt; ++newIt) { + if (newIt->isHidden) + continue; + result << InternalDataItem(*newIt); + } + + m_configuration = result; + + generateTree(); } -const ConfigModel::InternalDataItem &ConfigModel::itemAtRow(int row) const +static QString prefix(const QString &key) { - QTC_CHECK(row >= 0); - return m_configuration[row]; + int pos = key.indexOf('_'); + if (pos > 0) + return key.left(pos); + return key; +} + +void ConfigModel::generateTree() +{ + QList prefixList; + + // Generate nodes for *all* prefixes + QHash> prefixes; + for (const InternalDataItem &di : m_configuration) { + const QString p = prefix(di.key); + if (!prefixes.contains(p)) { + prefixes.insert(p, {}); + prefixList.append(p); + } + } + + // Fill prefix nodes: + for (InternalDataItem &di : m_configuration) + prefixes[prefix(di.key)].append(new Internal::ConfigModelTreeItem(&di)); + + Utils::TreeItem *root = new Utils::TreeItem; + + for (const QString &p : Utils::asConst(prefixList)) { + const QList &prefixItemList = prefixes.value(p); + QTC_ASSERT(!prefixItemList.isEmpty(), continue); + + if (prefixItemList.count() == 1) { + root->appendChild(prefixItemList.at(0)); + } else { + Utils::TreeItem *sti = new Utils::StaticTreeItem(p); + for (Utils::TreeItem *const ti : prefixItemList) + sti->appendChild(ti); + root->appendChild(sti); + } + prefixes.remove(p); + } + QTC_CHECK(prefixes.isEmpty()); + + setRootItem(root); } ConfigModel::InternalDataItem::InternalDataItem(const ConfigModel::DataItem &item) : DataItem(item) @@ -361,13 +250,18 @@ ConfigModel::InternalDataItem::InternalDataItem(const ConfigModel::DataItem &ite QString ConfigModel::InternalDataItem::toolTip() const { - QStringList tooltip(description); + QString desc = description; + if (isAdvanced) + desc += QCoreApplication::translate("CMakeProjectManager::ConfigModel", " (ADVANCED)"); + QStringList tooltip(desc); if (inCMakeCache) { if (value != newValue) tooltip << QCoreApplication::translate("CMakeProjectManager", "Current CMake: %1").arg(value); } else { tooltip << QCoreApplication::translate("CMakeProjectManager", "Not in CMakeCache.txt").arg(value); } + if (!kitValue.isEmpty()) + tooltip << QCoreApplication::translate("CMakeProjectManager::ConfigModel", "Current Kit: %1").arg(kitValue); return tooltip.join("
"); } @@ -376,4 +270,158 @@ QString ConfigModel::InternalDataItem::currentValue() const return isUserChanged ? newValue : value; } +namespace Internal { + +ConfigModelTreeItem::~ConfigModelTreeItem() = default; + +QVariant ConfigModelTreeItem::data(int column, int role) const +{ + QTC_ASSERT(column >= 0 && column < 2, return QVariant()); + + QTC_ASSERT(dataItem, return QVariant()); + + if (firstChild()) { + // Node with children: Only ever show name: + if (column == 0) + return dataItem->key; + return QVariant(); + } + + // Leaf node: + if (role == ConfigModel::ItemTypeRole) + return dataItem->type; + if (role == ConfigModel::ItemValuesRole) + return dataItem->values; + if (role == ConfigModel::ItemIsAdvancedRole) + return dataItem->isAdvanced ? "1" : "0"; + + switch (column) { + case 0: + switch (role) { + case Qt::DisplayRole: + return dataItem->key.isEmpty() ? QCoreApplication::translate("CMakeProjectManager::ConfigModel", "") : dataItem->key; + case Qt::EditRole: + return dataItem->key; + case Qt::ToolTipRole: + return toolTip(); + case Qt::FontRole: { + QFont font; + font.setItalic(dataItem->isCMakeChanged); + font.setBold(dataItem->isUserNew); + font.setStrikeOut(!dataItem->inCMakeCache && !dataItem->isUserNew); + return font; + } + default: + return QVariant(); + } + case 1: { + const QString value = currentValue(); + + switch (role) { + case Qt::CheckStateRole: + return (dataItem->type == ConfigModel::DataItem::BOOLEAN) + ? QVariant(isTrue(value) ? Qt::Checked : Qt::Unchecked) : QVariant(); + case Qt::DisplayRole: + return value; + case Qt::EditRole: + return (dataItem->type == ConfigModel::DataItem::BOOLEAN) ? QVariant(isTrue(value)) : QVariant(value); + case Qt::FontRole: { + QFont font; + font.setBold(dataItem->isUserChanged || dataItem->isUserNew); + font.setItalic(dataItem->isCMakeChanged); + return font; + } + case Qt::ForegroundRole: + return Utils::creatorTheme()->color((!dataItem->kitValue.isNull() && dataItem->kitValue != value) + ? Utils::Theme::TextColorHighlight : Utils::Theme::TextColorNormal); + case Qt::ToolTipRole: { + return toolTip(); + } + default: + return QVariant(); + } + } + default: + return QVariant(); + } +} + +bool ConfigModelTreeItem::setData(int column, const QVariant &value, int role) +{ + QTC_ASSERT(column >= 0 && column < 2, return false); + QTC_ASSERT(dataItem, return false); + + QString newValue = value.toString(); + if (role == Qt::CheckStateRole) { + if (column != 1) + return false; + newValue = QString::fromLatin1(value.toInt() == 0 ? "OFF" : "ON"); + } else if (role != Qt::EditRole) { + return false; + } + + switch (column) { + case 0: + if (!dataItem->key.isEmpty() && !dataItem->isUserNew) + return false; + dataItem->key = newValue; + dataItem->isUserNew = true; + dataItem->isUserChanged = false; + return true; + case 1: + if (dataItem->value == newValue) { + dataItem->newValue.clear(); + dataItem->isUserChanged = false; + } else { + dataItem->newValue = newValue; + dataItem->isUserChanged = true; + } + return true; + default: + return false; + } +} + +Qt::ItemFlags ConfigModelTreeItem::flags(int column) const +{ + if (column < 0 || column >= 2) + return Qt::NoItemFlags; + + QTC_ASSERT(dataItem, return Qt::NoItemFlags); + + if (column == 1) { + if (dataItem->type == ConfigModel::DataItem::BOOLEAN) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; + else + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; + } else { + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if (dataItem->isUserNew) + return flags |= Qt::ItemIsEditable; + return flags; + } +} + +QString ConfigModelTreeItem::toolTip() const +{ + QTC_ASSERT(dataItem, return QString()); + QStringList tooltip(dataItem->description); + if (!dataItem->kitValue.isEmpty()) + tooltip << QCoreApplication::translate("CMakeProjectManager", "Value requested by Kit: %1").arg(dataItem->kitValue); + if (dataItem->inCMakeCache) { + if (dataItem->value != dataItem->newValue) + tooltip << QCoreApplication::translate("CMakeProjectManager", "Current CMake: %1").arg(dataItem->value); + } else { + tooltip << QCoreApplication::translate("CMakeProjectManager", "Not in CMakeCache.txt"); + } + return tooltip.join("
"); +} + +QString ConfigModelTreeItem::currentValue() const +{ + QTC_ASSERT(dataItem, return QString()); + return dataItem->isUserChanged ? dataItem->newValue : dataItem->value; +} + +} // namespace Internal } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/configmodel.h b/src/plugins/cmakeprojectmanager/configmodel.h index f9629f702fa..f9b5b67ae69 100644 --- a/src/plugins/cmakeprojectmanager/configmodel.h +++ b/src/plugins/cmakeprojectmanager/configmodel.h @@ -26,17 +26,21 @@ #pragma once #include +#include namespace CMakeProjectManager { -class ConfigModel : public QAbstractTableModel +namespace Internal { class ConfigModelTreeItem; } + +class ConfigModel : public Utils::TreeModel<> { Q_OBJECT public: enum Roles { ItemTypeRole = Qt::UserRole, - ItemValuesRole + ItemValuesRole, + ItemIsAdvancedRole }; class DataItem { @@ -54,14 +58,9 @@ public: }; explicit ConfigModel(QObject *parent = nullptr); + ~ConfigModel() override; - // QAbstractItemModel interface - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &idx, int role) const final; void appendConfiguration(const QString &key, const QString &value = QString(), @@ -92,12 +91,35 @@ private: bool isUserNew = false; bool isCMakeChanged = false; QString newValue; + QString kitValue; }; - InternalDataItem &itemAtRow(int row); - const InternalDataItem &itemAtRow(int row) const; + void setConfiguration(const QList &config); + void generateTree(); + QList m_configuration; - QHash m_kitConfiguartion; + QHash m_kitConfiguration; + + friend class Internal::ConfigModelTreeItem; }; +namespace Internal { + +class ConfigModelTreeItem : public Utils::TreeItem +{ +public: + ConfigModelTreeItem(ConfigModel::InternalDataItem *di = nullptr) : dataItem(di) {} + virtual ~ConfigModelTreeItem() override; + + QVariant data(int column, int role) const final; + bool setData(int column, const QVariant &data, int role) final; + Qt::ItemFlags flags(int column) const final; + + QString toolTip() const; + QString currentValue() const; + + ConfigModel::InternalDataItem *dataItem; +}; + +} // namespace Internal } // namespace CMakeProjectManager