Wizards: add IconListField

- reuse some code from ComboBoxField throw an abstract ListField class
 - ListField can handle more data like: icon, trToolTip
 - fix disabledIndex in ComboBoxField
 - adjust documentation

Change-Id: I00b6ab787fb2fad97dafff32786cf73c636c772d
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
Tim Jenssen
2017-10-27 17:13:18 +02:00
parent 5c5bac67a4
commit 30d123e2ec
7 changed files with 357 additions and 124 deletions

View File

@@ -730,7 +730,10 @@
\endlist
\section2 Combo Box
\section2 List
\note The Combo Box and Icon List types are both variations of the List type,
and therefore they can have the same properties.
\code
{
@@ -744,19 +747,36 @@
}
},
\endcode
or
\code
{
"name": "ChosenBuildSystem",
"trDisplayName": "Choose your build system:",
"type": "IconList",
"data":
{
"items": [
{ "trKey": "Qbs", "value": "qbs", "icon": "qbs_icon.png", "trToolTip": "Building with Qbs." },
{ "trKey": "QMake", "value": "qmake", "icon": "qmake_icon.png", "trToolTip": "Building with QMake." }
]
}
},
\endcode
\list
\li \c items specifies a list of items to put into the combo box. The
list can contain both JSON objects and plain strings. For JSON
objects, define \c trKey and \c value pairs, where the \c trKey is
the list item visible to users and \c value contains the data
associated with the item.
\li \c items specifies a list of items to put into the list type. The
list can contain both JSON objects and plain strings.
For JSON objects, define \c trKey and \c value pairs, where the
\c trKey is the list item visible to users and \c value contains
the data associated with the item.
In addition, you can use \c icon to specify an icon for the list
item and \c trToolTip to specify a tooltip for it.
\li \c index specifies the index to select when the combo box is
\li \c index specifies the index to select when the list type is
enabled. By default, it is set to \c 0.
\li \c disabledIndex specifies the index to show if the combo box is
\li \c disabledIndex specifies the index to show if the list type is
disabled.
\endlist

View File

@@ -33,9 +33,9 @@
#include <utils/fancylineedit.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <utils/textfieldcombobox.h>
#include <utils/theme/theme.h>
#include <QComboBox>
#include <QCheckBox>
#include <QApplication>
#include <QDebug>
@@ -46,6 +46,10 @@
#include <QVariant>
#include <QVariantMap>
#include <QVBoxLayout>
#include <QListView>
#include <QStandardItem>
#include <QItemSelectionModel>
#include <QDir>
using namespace Utils;
@@ -792,49 +796,52 @@ void CheckBoxField::initializeData(MacroExpander *expander)
}
// --------------------------------------------------------------------
// ComboBoxFieldData:
// ListFieldData:
// --------------------------------------------------------------------
struct ComboBoxItem {
ComboBoxItem(const QString &k = QString(), const QString &v = QString(), const QVariant &c = true) :
key(k), value(v), condition(c)
{ }
QString key;
QString value;
QVariant condition;
};
ComboBoxItem parseComboBoxItem(const QVariant &item, QString *errorMessage)
std::unique_ptr<QStandardItem> createStandardItemFromListItem(const QVariant &item, QString *errorMessage)
{
if (item.type() == QVariant::List) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"No lists allowed inside ComboBox items list.");
return ComboBoxItem();
} else if (item.type() == QVariant::Map) {
"No JSON lists allowed inside List items.");
return {};
}
auto standardItem = std::make_unique<QStandardItem>();
if (item.type() == QVariant::Map) {
QVariantMap tmp = item.toMap();
QString key = JsonWizardFactory::localizedString(consumeValue(tmp, QLatin1String("trKey"), QString()).toString());
QString value = consumeValue(tmp, QLatin1String("value"), QString()).toString();
QVariant condition = consumeValue(tmp, QLatin1String("condition"), true);
const QString key = JsonWizardFactory::localizedString(consumeValue(tmp, "trKey", QString()).toString());
const QString value = consumeValue(tmp, "value", key).toString();
if (key.isNull() || key.isEmpty()) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"No \"key\" found in ComboBox items.");
return ComboBoxItem();
"No \"key\" found in List items.");
return {};
}
if (value.isNull())
value = key;
return ComboBoxItem(key, value, condition);
standardItem->setText(key);
standardItem->setData(value, ListField::ValueRole);
standardItem->setData(consumeValue(tmp, "condition", true), ListField::ConditionRole);
standardItem->setData(consumeValue(tmp, "icon"), ListField::IconStringRole);
standardItem->setToolTip(JsonWizardFactory::localizedString(consumeValue(tmp, "trToolTip", QString()).toString()));
warnAboutUnsupportedKeys(tmp, QString(), "List");
} else {
QString keyvalue = item.toString();
return ComboBoxItem(keyvalue, keyvalue);
const QString keyvalue = item.toString();
standardItem->setText(keyvalue);
standardItem->setData(keyvalue, ListField::ValueRole);
standardItem->setData(true, ListField::ConditionRole);
}
return standardItem;
}
bool ComboBoxField::parseData(const QVariant &data, QString *errorMessage)
ListField::ListField() = default;
ListField::~ListField() = default;
bool ListField::parseData(const QVariant &data, QString *errorMessage)
{
if (data.type() != QVariant::Map) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"ComboBox data is not an object.");
"%1(\"%2\") data is not an object.")
.arg(type(), name());
return false;
}
@@ -844,118 +851,235 @@ bool ComboBoxField::parseData(const QVariant &data, QString *errorMessage)
m_index = consumeValue(tmp, "index", 0).toInt(&ok);
if (!ok) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"ComboBox(\"%1\") \"index\" is not an integer value.")
.arg(name());
"%1(\"%2\") \"index\" is not an integer value.")
.arg(type(), name());
return false;
}
m_disabledIndex = consumeValue(tmp, "disabledIndex", -1).toInt(&ok);
if (!ok) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"ComboBox(\"%1\") \"disabledIndex\" is not an integer value.")
.arg(name());
"%1(\"%2\") \"disabledIndex\" is not an integer value.")
.arg(type(), name());
return false;
}
QVariant value = consumeValue(tmp, "items");
const QVariant value = consumeValue(tmp, "items");
if (value.isNull()) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"ComboBox(\"%1\") \"items\" missing.")
.arg(name());
"%1(\"%2\") \"items\" missing.")
.arg(type(), name());
return false;
}
if (value.type() != QVariant::List) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"ComboBox(\"%1\") \"items\" is not a list.")
.arg(name());
"%1(\"%2\") \"items\" is not a JSON list.")
.arg(type(), name());
return false;
}
foreach (const QVariant &i, value.toList()) {
ComboBoxItem keyValue = parseComboBoxItem(i, errorMessage);
if (keyValue.key.isNull())
return false; // an error happened...
m_itemList.append(keyValue.key);
m_itemDataList.append(keyValue.value);
m_itemConditionList.append(keyValue.condition);
for (const QVariant &i : value.toList()) {
std::unique_ptr<QStandardItem> item = createStandardItemFromListItem(i, errorMessage);
QString test = item->text();
QTC_ASSERT(!item || !item->text().isEmpty(), continue);
m_itemList.emplace_back(std::move(item));
}
if (m_itemConditionList.count() != m_itemDataList.count()
|| m_itemConditionList.count() != m_itemList.count()) {
m_itemConditionList.clear();
m_itemDataList.clear();
m_itemList.clear();
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"Internal Error: ComboBox(\"%1\") items lists got mixed up.")
.arg(name());
return false;
}
warnAboutUnsupportedKeys(tmp, name(), type());
return true;
}
QWidget *ComboBoxField::createWidget(const QString &displayName, JsonFieldPage *page)
{
Q_UNUSED(displayName);
Q_UNUSED(page);
return new TextFieldComboBox;
}
void ComboBoxField::setup(JsonFieldPage *page, const QString &name)
{
auto w = qobject_cast<TextFieldComboBox *>(widget());
QTC_ASSERT(w, return);
page->registerFieldWithName(name, w, "indexText", SIGNAL(text4Changed(QString)));
QObject::connect(w, &TextFieldComboBox::text4Changed,
page, [page](QString) { page->completeChanged(); });
}
bool ComboBoxField::validate(MacroExpander *expander, QString *message)
bool ListField::validate(MacroExpander *expander, QString *message)
{
if (!JsonFieldPage::Field::validate(expander, message))
return false;
auto w = qobject_cast<TextFieldComboBox *>(widget());
QTC_ASSERT(w, return false);
if (!w->isEnabled() && m_disabledIndex >= 0 && m_savedIndex < 0) {
m_savedIndex = w->currentIndex();
w->setCurrentIndex(m_disabledIndex);
} else if (w->isEnabled() && m_savedIndex >= 0) {
w->setCurrentIndex(m_savedIndex);
m_savedIndex = -1;
updateIndex();
if (selectionModel()->hasSelection())
return true;
return false;
}
void ListField::initializeData(MacroExpander *expander)
{
QTC_ASSERT(widget(), return);
QStandardItem *currentItem = m_index >= 0 ? m_itemList[uint(m_index)].get() : nullptr;
QList<QStandardItem*> expandedValuesItems;
expandedValuesItems.reserve(int(m_itemList.size()));
QSize maxIconSize;
for (const std::unique_ptr<QStandardItem> &item : m_itemList) {
bool condition = JsonWizard::boolFromVariant(item->data(ConditionRole), expander);
if (!condition)
continue;
QStandardItem *expandedValuesItem = item->clone();
if (item.get() == currentItem)
currentItem = expandedValuesItem;
expandedValuesItem->setText(expander->expand(item->text()));
expandedValuesItem->setData(expander->expand(item->data(ValueRole).toString()), ValueRole);
expandedValuesItem->setData(expander->expand(item->data(IconStringRole).toString()), IconStringRole);
expandedValuesItem->setData(condition, ConditionRole);
QString iconPath = expandedValuesItem->data(IconStringRole).toString();
if (!iconPath.isEmpty()) {
if (JsonFieldPage *page = qobject_cast<JsonFieldPage*>(widget()->parentWidget())) {
const QString wizardDirectory = page->value("WizardDir").toString();
iconPath = QDir::cleanPath(QDir(wizardDirectory).absoluteFilePath(iconPath));
if (QFileInfo::exists(iconPath)) {
QIcon icon(iconPath);
expandedValuesItem->setIcon(icon);
addPossibleIconSize(icon);
} else {
qWarning().noquote() << QString("Icon file \"%1\" not found.").arg(QDir::toNativeSeparators(iconPath));
}
} else {
qWarning().noquote() << QString("%1(\"%2\") has no parentWidget JsonFieldPage to get the icon path.").arg(type(), name());
}
}
expandedValuesItems.append(expandedValuesItem);
}
return true;
itemModel()->clear();
itemModel()->appendColumn(expandedValuesItems); // inserts the first column
selectionModel()->setCurrentIndex(itemModel()->indexFromItem(currentItem), QItemSelectionModel::ClearAndSelect);
updateIndex();
}
QStandardItemModel *ListField::itemModel()
{
if (!m_itemModel)
m_itemModel = new QStandardItemModel(widget());
return m_itemModel;
}
QItemSelectionModel *ListField::selectionModel()
{
return m_selectionModel;
}
void ListField::setSelectionModel(QItemSelectionModel *selectionModel)
{
m_selectionModel = selectionModel;
}
QSize ListField::maxIconSize()
{
return m_maxIconSize;
}
void ListField::addPossibleIconSize(const QIcon &icon)
{
const QSize iconSize = icon.availableSizes().value(0);
if (iconSize.height() > m_maxIconSize.height())
m_maxIconSize = iconSize;
}
void ListField::updateIndex()
{
if (!widget()->isEnabled() && m_disabledIndex >= 0 && m_savedIndex < 0) {
m_savedIndex = selectionModel()->currentIndex().row();
selectionModel()->setCurrentIndex(itemModel()->index(m_disabledIndex, 0), QItemSelectionModel::ClearAndSelect);
} else if (widget()->isEnabled() && m_savedIndex >= 0) {
selectionModel()->setCurrentIndex(itemModel()->index(m_savedIndex, 0), QItemSelectionModel::ClearAndSelect);
m_savedIndex = -1;
}
}
void ComboBoxField::setup(JsonFieldPage *page, const QString &name)
{
auto w = qobject_cast<QComboBox*>(widget());
QTC_ASSERT(w, return);
w->setModel(itemModel());
w->setInsertPolicy(QComboBox::NoInsert);
QSizePolicy s = w->sizePolicy();
s.setHorizontalPolicy(QSizePolicy::Expanding);
w->setSizePolicy(s);
setSelectionModel(w->view()->selectionModel());
// the selectionModel does not behave like expected and wanted - so we block signals here
// (for example there was some losing focus thing when hovering over items, ...)
selectionModel()->blockSignals(true);
QObject::connect(w, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), [w, this](int index) {
w->blockSignals(true);
selectionModel()->clearSelection();
selectionModel()->blockSignals(false);
selectionModel()->setCurrentIndex(w->model()->index(index, 0),
QItemSelectionModel::ClearAndSelect);
selectionModel()->blockSignals(true);
w->blockSignals(false);
});
page->registerObjectAsFieldWithName<QItemSelectionModel>(name, selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
const QModelIndex i = selectionModel()->currentIndex();
if (i.isValid())
return i.data(ValueRole).toString();
return QString();
});
QObject::connect(selectionModel(), &QItemSelectionModel::selectionChanged, page, [page]() {
emit page->completeChanged();
});
}
QWidget *ComboBoxField::createWidget(const QString & /*displayName*/, JsonFieldPage * /*page*/)
{
return new QComboBox;
}
void ComboBoxField::initializeData(MacroExpander *expander)
{
auto w = qobject_cast<TextFieldComboBox *>(widget());
QTC_ASSERT(widget(), return);
QStringList tmpItems
= Utils::transform(m_itemList,
[expander](const QString &i) { return expander->expand(i); });
QStringList tmpData
= Utils::transform(m_itemDataList,
[expander](const QString &i) { return expander->expand(i); });
QList<bool> tmpConditions
= Utils::transform(m_itemConditionList,
[expander](const QVariant &v) { return JsonWizard::boolFromVariant(v, expander); });
ListField::initializeData(expander);
// refresh also the current text of the combobox
auto w = qobject_cast<QComboBox*>(widget());
w->setCurrentIndex(selectionModel()->currentIndex().row());
}
int index = m_index;
for (int i = tmpConditions.count() - 1; i >= 0; --i) {
if (!tmpConditions.at(i)) {
tmpItems.removeAt(i);
tmpData.removeAt(i);
if (i < index && index > 0)
--index;
}
}
void IconListField::setup(JsonFieldPage *page, const QString &name)
{
auto w = qobject_cast<QListView*>(widget());
QTC_ASSERT(w, return);
if (index < 0 || index >= tmpData.count())
index = 0;
w->setItems(tmpItems, tmpData);
w->setInsertPolicy(QComboBox::NoInsert);
w->setCurrentIndex(index);
w->setViewMode(QListView::IconMode);
w->setMovement(QListView::Static);
w->setResizeMode(QListView::Adjust);
w->setSelectionRectVisible(false);
w->setWrapping(true);
w->setWordWrap(true);
w->setModel(itemModel());
setSelectionModel(w->selectionModel());
page->registerObjectAsFieldWithName<QItemSelectionModel>(name, selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
const QModelIndex i = selectionModel()->currentIndex();
if (i.isValid())
return i.data(ValueRole).toString();
return QString();
});
QObject::connect(selectionModel(), &QItemSelectionModel::selectionChanged, page, [page]() {
page->completeChanged();
});
}
QWidget *IconListField::createWidget(const QString & /*displayName*/, JsonFieldPage * /*page*/)
{
return new QListView;
}
void IconListField::initializeData(MacroExpander *expander)
{
ListField::initializeData(expander);
auto w = qobject_cast<QListView*>(widget());
const int spacing = 4;
w->setSpacing(spacing);
w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// adding a third hight of the icon to see following items if there are some
w->setMinimumHeight(maxIconSize().height() + maxIconSize().height() / 3);
w->setIconSize(maxIconSize());
}
// --------------------------------------------------------------------

View File

@@ -42,7 +42,6 @@ QT_END_NAMESPACE
namespace Utils {
class MacroExpander;
class TextFieldComboBox;
} // namespace Utils
namespace ProjectExplorer {

View File

@@ -32,6 +32,16 @@
#include <QWidget>
#include <QString>
#include <QVariant>
#include <QDir>
#include <memory>
#include <vector>
QT_BEGIN_NAMESPACE
class QStandardItem;
class QStandardItemModel;
class QItemSelectionModel;
QT_END_NAMESPACE
namespace ProjectExplorer {
@@ -170,25 +180,58 @@ private:
bool m_isModified = false;
};
class ComboBoxField : public JsonFieldPage::Field
class ListField : public JsonFieldPage::Field
{
private:
public:
enum SpecialRoles {
ValueRole = Qt::UserRole,
ConditionRole = Qt::UserRole + 1,
IconStringRole = Qt::UserRole + 2
};
ListField();
virtual ~ListField() override;
protected:
bool parseData(const QVariant &data, QString *errorMessage) override;
QWidget *createWidget(const QString &displayName, JsonFieldPage *page) override;
void setup(JsonFieldPage *page, const QString &name) override;
QWidget *createWidget(const QString &displayName, JsonFieldPage *page) override = 0;
void setup(JsonFieldPage *page, const QString &name) override = 0;
bool validate(Utils::MacroExpander *expander, QString *message) override;
void initializeData(Utils::MacroExpander *expander) override;
QStandardItemModel *itemModel();
QItemSelectionModel *selectionModel();
void setSelectionModel(QItemSelectionModel *selectionModel);
QSize maxIconSize();
QStringList m_itemList;
QStringList m_itemDataList;
QVariantList m_itemConditionList;
private:
void addPossibleIconSize(const QIcon &icon);
void updateIndex();
std::vector<std::unique_ptr<QStandardItem>> m_itemList;
QStandardItemModel *m_itemModel = nullptr;
QItemSelectionModel *m_selectionModel = nullptr;
int m_index = -1;
int m_disabledIndex = -1;
QSize m_maxIconSize;
mutable int m_savedIndex = -1;
};
class ComboBoxField : public ListField
{
public:
void setup(JsonFieldPage *page, const QString &name) override;
QWidget *createWidget(const QString &displayName, JsonFieldPage *page) override;
void initializeData(Utils::MacroExpander *expander) override;
};
class IconListField : public ListField
{
public:
void setup(JsonFieldPage *page, const QString &name) override;
QWidget *createWidget(const QString &displayName, JsonFieldPage *page) override;
void initializeData(Utils::MacroExpander *expander) override;
};
} // namespace ProjectExplorer

View File

@@ -34,6 +34,7 @@
#include <QCheckBox>
#include <QLineEdit>
#include <QComboBox>
#include <QListView>
#include <functional>
namespace {
@@ -209,8 +210,8 @@ void ProjectExplorer::ProjectExplorerPlugin::testJsonWizardsLineEdit()
void ProjectExplorer::ProjectExplorerPlugin::testJsonWizardsComboBox()
{
QString errorMessage;
QWidget parent;
const QJsonArray items({"abc", "cde", "fgh"});
QJsonObject disabledComboBoxObject = createWidget("ComboBox", "Disabled", QJsonObject({ {{"disabledIndex", 2}, {"items", items}} }));
disabledComboBoxObject.insert("enabled", false);
@@ -228,6 +229,8 @@ void ProjectExplorer::ProjectExplorerPlugin::testJsonWizardsComboBox()
QComboBox *defaultComboBox = findComboBox(wizard, "Default");
QVERIFY(defaultComboBox);
QCOMPARE(defaultComboBox->count(), items.count());
QCOMPARE(qPrintable(defaultComboBox->currentText()), "abc");
defaultComboBox->setCurrentIndex(2);
QCOMPARE(qPrintable(defaultComboBox->currentText()), "fgh");
@@ -238,7 +241,49 @@ void ProjectExplorer::ProjectExplorerPlugin::testJsonWizardsComboBox()
QComboBox *disabledComboBox = findComboBox(wizard, "Disabled");
QVERIFY(disabledComboBox);
QEXPECT_FAIL("", "This is wrong, since ComboBox got condition items", Continue);
QCOMPARE(qPrintable(disabledComboBox->currentText()), "fgh");
}
void ProjectExplorer::ProjectExplorerPlugin::testJsonWizardsIconList()
{
QString errorMessage;
QWidget parent;
const QJsonArray items({
QJsonObject{
{"trKey", "item no1"},
{"condition", true},
{"icon", "../share/qtcreator/templates/wizards/qtquickstyleicons/default.png"}
},
QJsonObject{
{"trKey", "item no2"},
{"condition", false},
{"icon", "not_existing_path"}
},
QJsonObject{
{"trKey", "item no3"},
{"condition", true},
{"trToolTip", "MyToolTip"},
{"icon", "../share/qtcreator/templates/wizards/qtquickstyleicons/default.png"}
}
});
const QJsonArray widgets({
createWidget("IconList", "Fancy", QJsonObject{{"index", -1}, {"items", items}})
});
const QJsonObject pages = createFieldPageJsonObject(widgets);
const QJsonObject wizardObject = createGeneralWizard(pages);
JsonWizardFactory *factory = ProjectExplorer::JsonWizardFactory::createWizardFactory(wizardObject.toVariantMap(), QDir(), &errorMessage);
QVERIFY2(factory, qPrintable(errorMessage));
Utils::Wizard *wizard = factory->runWizard(QString(), &parent, Core::Id(), QVariantMap());
auto view = wizard->findChild<QListView *>("FancyIconList");
QCOMPARE(view->model()->rowCount(), 2);
QVERIFY(view->model()->index(0,0).data(Qt::DecorationRole).canConvert<QIcon>());
QIcon icon = view->model()->index(0,0).data(Qt::DecorationRole).value<QIcon>();
QVERIFY(!icon.isNull());
QVERIFY(!wizard->page(0)->isComplete());
}

View File

@@ -57,6 +57,7 @@ FieldPageFactory::FieldPageFactory()
JsonFieldPage::registerFieldFactory(QLatin1String("PathChooser"), []() { return new PathChooserField; });
JsonFieldPage::registerFieldFactory(QLatin1String("CheckBox"), []() { return new CheckBoxField; });
JsonFieldPage::registerFieldFactory(QLatin1String("ComboBox"), []() { return new ComboBoxField; });
JsonFieldPage::registerFieldFactory(QLatin1String("IconList"), []() { return new IconListField; });
}
Utils::WizardPage *FieldPageFactory::create(JsonWizard *wizard, Core::Id typeId, const QVariant &data)

View File

@@ -189,6 +189,7 @@ private slots:
void testJsonWizardsCheckBox();
void testJsonWizardsLineEdit();
void testJsonWizardsComboBox();
void testJsonWizardsIconList();
void testAnsiFilterOutputParser_data();
void testAnsiFilterOutputParser();