Commit 28e43068 authored by Tobias Hunger's avatar Tobias Hunger

CMake: Add UI to display and edit cmake configuration

Change-Id: I95944dcd58dc86023c2757068979f92b8989011b
Reviewed-by: default avatarTim Jenssen <tim.jenssen@theqtcompany.com>
parent f71b3652
......@@ -138,6 +138,12 @@ void BuildDirManager::forceReparse()
startCMake(tool, generator, m_inputConfig);
}
void BuildDirManager::setInputConfiguration(const CMakeConfig &config)
{
m_inputConfig = config;
forceReparse();
}
void BuildDirManager::parse()
{
CMakeTool *tool = CMakeKitInformation::cmakeTool(m_kit);
......
......@@ -74,6 +74,8 @@ public:
void parse();
void forceReparse();
void setInputConfiguration(const CMakeConfig &config);
bool isProjectFile(const Utils::FileName &fileName) const;
QString projectName() const;
QList<CMakeBuildTarget> buildTargets() const;
......
......@@ -24,24 +24,46 @@
****************************************************************************/
#include "cmakebuildsettingswidget.h"
#include "configmodel.h"
#include "cmakeproject.h"
#include "cmakebuildconfiguration.h"
#include <coreplugin/icore.h>
#include <coreplugin/find/itemviewfind.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/target.h>
#include <utils/detailswidget.h>
#include <utils/headerviewstretcher.h>
#include <utils/pathchooser.h>
#include <utils/itemviews.h>
#include <QFormLayout>
#include <QBoxLayout>
#include <QCheckBox>
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QSpacerItem>
namespace CMakeProjectManager {
namespace Internal {
// --------------------------------------------------------------------
// CMakeBuildSettingsWidget:
// --------------------------------------------------------------------
CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) :
m_buildConfiguration(bc)
m_buildConfiguration(bc),
m_configModel(new ConfigModel(this)),
m_configFilterModel(new QSortFilterProxyModel)
{
QTC_CHECK(bc);
setDisplayName(tr("CMake"));
auto vbox = new QVBoxLayout(this);
vbox->setMargin(0);
auto container = new Utils::DetailsWidget;
......@@ -51,22 +73,128 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc)
auto details = new QWidget(container);
container->setWidget(details);
auto fl = new QFormLayout(details);
fl->setMargin(0);
fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
auto mainLayout = new QGridLayout(details);
mainLayout->setMargin(0);
mainLayout->setColumnStretch(1, 10);
auto project = static_cast<CMakeProject *>(bc->target()->project());
auto buildDirChooser = new Utils::PathChooser;
buildDirChooser->setBaseFileName(project->projectDirectory());
buildDirChooser->setFileName(bc->buildDirectory());
connect(buildDirChooser, &Utils::PathChooser::rawPathChanged, this,
[this, project](const QString &path) {
m_configModel->flush(); // clear out config cache...
project->changeBuildDirectory(m_buildConfiguration, path);
});
fl->addRow(tr("Build directory:"), buildDirChooser);
int row = 0;
mainLayout->addWidget(new QLabel(tr("Build directory:")), row, 0);
mainLayout->addWidget(buildDirChooser->lineEdit(), row, 1);
mainLayout->addWidget(buildDirChooser->buttonAtIndex(0), row, 2);
setDisplayName(tr("CMake"));
++row;
mainLayout->addItem(new QSpacerItem(20, 20), row, 0);
++row;
auto tree = new Utils::TreeView;
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_configView->setModel(m_configFilterModel);
m_configView->setMinimumHeight(300);
m_configView->setRootIsDecorated(false);
m_configView->setUniformRowHeights(true);
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
QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(m_configView, Core::ItemViewFind::LightColored);
findWrapper->setFrameStyle(QFrame::StyledPanel);
m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicator::Large, findWrapper);
m_progressIndicator->attachToWidget(findWrapper);
m_progressIndicator->raise();
m_progressIndicator->hide();
m_showProgressTimer.setSingleShot(true);
m_showProgressTimer.setInterval(50); // don't show progress for < 50ms tasks
connect(&m_showProgressTimer, &QTimer::timeout, [this]() { m_progressIndicator->show(); });
mainLayout->addWidget(findWrapper, row, 0, 1, 2);
auto buttonLayout = new QVBoxLayout;
m_editButton = new QPushButton(tr("&Edit"));
buttonLayout->addWidget(m_editButton);
m_resetButton = new QPushButton(tr("&Reset"));
m_resetButton->setEnabled(false);
buttonLayout->addWidget(m_resetButton);
buttonLayout->addItem(new QSpacerItem(10, 10, QSizePolicy::Fixed, QSizePolicy::Fixed));
m_showAdvancedCheckBox = new QCheckBox(tr("Advanced"));
buttonLayout->addWidget(m_showAdvancedCheckBox);
buttonLayout->addItem(new QSpacerItem(10, 10, QSizePolicy::Minimum, QSizePolicy::Expanding));
mainLayout->addLayout(buttonLayout, row, 2);
++row;
m_reconfigureButton = new QPushButton(tr("Apply Configuration Changes"));
m_reconfigureButton->setEnabled(false);
mainLayout->addWidget(m_reconfigureButton, row, 0, 1, 3);
updateAdvancedCheckBox();
connect(project, &CMakeProject::parsingStarted, this, [this]() {
updateButtonState();
m_showProgressTimer.start();
});
connect(project, &CMakeProject::buildDirectoryDataAvailable,
this, [this, project, buildDirChooser](ProjectExplorer::BuildConfiguration *bc) {
updateButtonState();
if (m_buildConfiguration == bc) {
m_configModel->setConfiguration(project->currentCMakeConfiguration());
buildDirChooser->triggerChanged(); // refresh valid state...
}
m_showProgressTimer.stop();
m_progressIndicator->hide();
});
connect(m_configModel, &QAbstractItemModel::dataChanged,
this, &CMakeBuildSettingsWidget::updateButtonState);
connect(m_configModel, &QAbstractItemModel::modelReset,
this, &CMakeBuildSettingsWidget::updateButtonState);
connect(m_showAdvancedCheckBox, &QCheckBox::stateChanged,
this, &CMakeBuildSettingsWidget::updateAdvancedCheckBox);
connect(m_resetButton, &QPushButton::clicked, m_configModel, &ConfigModel::resetAllChanges);
connect(m_reconfigureButton, &QPushButton::clicked, this, [this, project]() {
project->setCurrentCMakeConfiguration(m_configModel->configurationChanges());
});
connect(m_editButton, &QPushButton::clicked, this, [this]() {
QModelIndex idx = m_configView->currentIndex();
if (idx.column() != 1)
idx = idx.sibling(idx.row(), 1);
m_configView->setCurrentIndex(idx);
m_configView->edit(idx);
});
}
void CMakeBuildSettingsWidget::updateButtonState()
{
auto project = static_cast<CMakeProject *>(m_buildConfiguration->target()->project());
const bool isParsing = project->isParsing();
const bool hasChanges = m_configModel->hasChanges();
m_resetButton->setEnabled(hasChanges && !isParsing);
m_reconfigureButton->setEnabled((hasChanges || m_configModel->hasCMakeChanges()) && !isParsing);
}
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);
}
} // namespace Internal
......
......@@ -27,7 +27,21 @@
#include <projectexplorer/namedwidget.h>
#include <utils/progressindicator.h>
#include <QTimer>
QT_BEGIN_NAMESPACE
class QCheckBox;
class QPushButton;
class QTreeView;
class QSortFilterProxyModel;
QT_END_NAMESPACE
namespace CMakeProjectManager {
class ConfigModel;
namespace Internal {
class CMakeBuildConfiguration;
......@@ -39,7 +53,19 @@ public:
CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc);
private:
CMakeBuildConfiguration *m_buildConfiguration = 0;
void updateButtonState();
void updateAdvancedCheckBox();
CMakeBuildConfiguration *m_buildConfiguration;
QTreeView *m_configView;
ConfigModel *m_configModel;
QSortFilterProxyModel *m_configFilterModel;
Utils::ProgressIndicator *m_progressIndicator;
QPushButton *m_editButton;
QPushButton *m_resetButton;
QCheckBox *m_showAdvancedCheckBox;
QPushButton *m_reconfigureButton;
QTimer m_showProgressTimer;
};
} // namespace Internal
......
......@@ -339,6 +339,75 @@ bool CMakeProject::isParsing() const
return m_buildDirManager && m_buildDirManager->isBusy();
}
QList<ConfigModel::DataItem> CMakeProject::currentCMakeConfiguration() const
{
if (!m_buildDirManager || m_buildDirManager->isBusy())
return QList<ConfigModel::DataItem>();
const QList<CMakeConfigItem> cmakeItems = m_buildDirManager->configuration();
return Utils::transform(cmakeItems, [](const CMakeConfigItem &i) {
ConfigModel::DataItem j;
j.key = QString::fromUtf8(i.key);
j.value = QString::fromUtf8(i.value);
j.description = QString::fromUtf8(i.documentation);
j.isAdvanced = i.isAdvanced;
switch (i.type) {
case CMakeConfigItem::FILEPATH:
j.type = ConfigModel::DataItem::FILE;
break;
case CMakeConfigItem::PATH:
j.type = ConfigModel::DataItem::DIRECTORY;
break;
case CMakeConfigItem::BOOL:
j.type = ConfigModel::DataItem::BOOLEAN;
break;
case CMakeConfigItem::STRING:
j.type = ConfigModel::DataItem::STRING;
break;
default:
j.type = ConfigModel::DataItem::UNKNOWN;
break;
}
return j;
});
}
void CMakeProject::setCurrentCMakeConfiguration(const QList<ConfigModel::DataItem> &items)
{
if (!m_buildDirManager || m_buildDirManager->isBusy())
return;
const CMakeConfig config = Utils::transform(items, [](const ConfigModel::DataItem &i) {
CMakeConfigItem ni;
ni.key = i.key.toUtf8();
ni.value = i.value.toUtf8();
ni.documentation = i.description.toUtf8();
ni.isAdvanced = i.isAdvanced;
switch (i.type) {
case CMakeProjectManager::ConfigModel::DataItem::BOOLEAN:
ni.type = CMakeConfigItem::BOOL;
break;
case CMakeProjectManager::ConfigModel::DataItem::FILE:
ni.type = CMakeConfigItem::FILEPATH;
break;
case CMakeProjectManager::ConfigModel::DataItem::DIRECTORY:
ni.type = CMakeConfigItem::PATH;
break;
case CMakeProjectManager::ConfigModel::DataItem::STRING:
ni.type = CMakeConfigItem::STRING;
break;
case CMakeProjectManager::ConfigModel::DataItem::UNKNOWN:
default:
ni.type = CMakeConfigItem::INTERNAL;
break;
}
return ni;
});
m_buildDirManager->setInputConfiguration(config);
}
bool CMakeProject::isProjectFile(const FileName &fileName)
{
if (!m_buildDirManager)
......
......@@ -27,6 +27,7 @@
#include "cmake_global.h"
#include "cmakeprojectnodes.h"
#include "configmodel.h"
#include <projectexplorer/project.h>
#include <projectexplorer/buildconfiguration.h>
......@@ -112,6 +113,9 @@ public:
void runCMake();
bool isParsing() const;
QList<ConfigModel::DataItem> currentCMakeConfiguration() const;
void setCurrentCMakeConfiguration(const QList<ConfigModel::DataItem> &items);
signals:
/// emitted when parsing starts:
void parsingStarted();
......
......@@ -26,7 +26,8 @@ HEADERS = builddirmanager.h \
cmakefile.h \
cmakebuildsettingswidget.h \
cmakeindenter.h \
cmakeautocompleter.h
cmakeautocompleter.h \
configmodel.h
SOURCES = builddirmanager.cpp \
cmakebuildstep.cpp \
......@@ -50,6 +51,7 @@ SOURCES = builddirmanager.cpp \
cmakefile.cpp \
cmakebuildsettingswidget.cpp \
cmakeindenter.cpp \
cmakeautocompleter.cpp
cmakeautocompleter.cpp \
configmodel.cpp
RESOURCES += cmakeproject.qrc
......@@ -66,6 +66,8 @@ QtcPlugin {
"cmakeindenter.h",
"cmakeindenter.cpp",
"cmakeautocompleter.h",
"cmakeautocompleter.cpp"
"cmakeautocompleter.cpp",
"configmodel.cpp",
"configmodel.h"
]
}
/****************************************************************************
**
** Copyright (C) 2016 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 "configmodel.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QFont>
namespace CMakeProjectManager {
static bool isTrue(const QString &value)
{
const QString lower = value.toLower();
return lower == QStringLiteral("true") || lower == QStringLiteral("on") || lower == QStringLiteral("1");
}
ConfigModel::ConfigModel(QObject *parent) : QAbstractTableModel(parent)
{ }
int ConfigModel::rowCount(const QModelIndex &parent) const
{
QTC_ASSERT(parent.model() == nullptr || parent.model() == this, return 0);
if (parent.isValid())
return 0;
return m_configuration.count();
}
int ConfigModel::columnCount(const QModelIndex &parent) const
{
QTC_ASSERT(!parent.isValid(), return 0);
QTC_ASSERT(parent.model() == nullptr, return 0);
return 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 {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
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()];
switch (index.column()) {
case 0:
switch (role) {
case Qt::DisplayRole:
return item.key.isEmpty() ? tr("<UNSET>") : item.key;
case Qt::EditRole:
return item.key;
case Qt::ToolTipRole:
return item.description;
case Qt::UserRole:
return item.type;
case Qt::FontRole: {
QFont font;
font.setItalic(item.isCMakeChanged);
font.setBold(item.isUserNew);
return font;
}
default:
return QVariant();
}
case 1: {
const QString value = item.isUserChanged ? item.newValue : item.value;
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::ToolTipRole:
return item.description;
case Qt::UserRole:
return item.type;
default:
return QVariant();
}
}
case 2:
switch (role) {
case Qt::EditRole:
return QString::fromLatin1("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)
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())
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;
}
}
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();
}
}
void ConfigModel::setConfiguration(const QList<ConfigModel::DataItem> &config)
{
QList<DataItem> 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<InternalDataItem> result;
while (newIt != newEndIt && oldIt != oldEndIt) {
if (newIt->key < oldIt->key) {
// Add new entry:
result << InternalDataItem(*newIt);
++newIt;
} else if (newIt->key > oldIt->key) {
// skip old entry:
++oldIt;
} else {
// merge old/new entry:
InternalDataItem item(*newIt);
item.isCMakeChanged = (oldIt->value != newIt->value);
result << item;
++newIt;
++oldIt;
}
}
// Add remaining new entries:
for (; newIt != newEndIt; ++newIt)
result << InternalDataItem(*newIt);