QGIM: implement itemData/setItemData for associative containers
If the item at index is an associative container, then we can operate directly on that container. Otherwise, call the base class implementation. In contrast to the default implementation, the setItemData implementation is transactional: nothing will be written to a multi-role storage unless all entries could be written. Change-Id: I883c647dac82b3a068afd77b36c245525d59b44b Reviewed-by: Artem Dyomin <artem.dyomin@qt.io>
This commit is contained in:
parent
9f63577ddd
commit
dc3fb041e9
@ -108,13 +108,15 @@ QT_BEGIN_NAMESPACE
|
||||
\l{Qt::ItemDataRole}, or QString as the key type, and QVariant as the
|
||||
mapped type, then QGenericItemModel interprets that container as the storage
|
||||
of the data for multiple roles. The data() and setData() functions return
|
||||
and modify the mapped value in the container, and clearItemData() clears
|
||||
the entire container.
|
||||
and modify the mapped value in the container, and setItemData() modifies all
|
||||
provided values, itemData() returns all stored values, and clearItemData()
|
||||
clears the entire container.
|
||||
|
||||
\snippet qgenericitemmodel/main.cpp color_map
|
||||
|
||||
The most efficient data type to use as the key is Qt::ItemDataRole or
|
||||
\c{int}.
|
||||
\c{int}. When using \c{int}, itemData() returns the container as is, and
|
||||
doesn't have to create a copy of the data.
|
||||
|
||||
\section2 The C++ tuple protocol
|
||||
|
||||
@ -315,6 +317,48 @@ bool QGenericItemModel::setData(const QModelIndex &index, const QVariant &data,
|
||||
return impl->call<bool>(QGenericItemModelImplBase::SetData, index, data, role);
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
|
||||
Returns a map with values for all predefined roles in the model for the
|
||||
item at the given \a index.
|
||||
|
||||
If the item type for that \a index is an associative container that maps
|
||||
from either \c{int}, Qt::ItemDataRole, or QString to a QVariant, then the
|
||||
data from that container is returned.
|
||||
|
||||
\sa setItemData(), Qt::ItemDataRole, data()
|
||||
*/
|
||||
QMap<int, QVariant> QGenericItemModel::itemData(const QModelIndex &index) const
|
||||
{
|
||||
return impl->callConst<QMap<int, QVariant>>(QGenericItemModelImplBase::ItemData, index);
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
|
||||
If the item type for that \a index is an associative container that maps
|
||||
from either \c{int} or Qt::ItemDataRole to a QVariant, then the entries in
|
||||
\a data are stored in that container. If the associative container maps from
|
||||
QString to QVariant, then only those values in \a data are stored for which
|
||||
there is a mapping in the \l{roleNames()}{role names} table.
|
||||
|
||||
Roles for which there is no entry in \a data are not modified.
|
||||
|
||||
This implementation is transactional, and returns true if all the entries
|
||||
from \a data could be stored. If any entry could not be updated, then the
|
||||
original container is not modified at all, and the function returns false.
|
||||
|
||||
If the item is not an associative container, then this calls the base class
|
||||
implementation, which calls setData() for each entry in \a data.
|
||||
|
||||
\sa itemData(), setData(), Qt::ItemDataRole
|
||||
*/
|
||||
bool QGenericItemModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &data)
|
||||
{
|
||||
return impl->call<bool>(QGenericItemModelImplBase::SetItemData, index, data);
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#define QGENERICITEMMODEL_H
|
||||
|
||||
#include <QtCore/qgenericitemmodel_impl.h>
|
||||
#include <QtCore/qmap.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -26,6 +27,8 @@ public:
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) override;
|
||||
QMap<int, QVariant> itemData(const QModelIndex &index) const override;
|
||||
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &data) override;
|
||||
bool clearItemData(const QModelIndex &index) override;
|
||||
bool insertColumns(int column, int count, const QModelIndex &parent = {}) override;
|
||||
bool removeColumns(int column, int count, const QModelIndex &parent = {}) override;
|
||||
@ -170,6 +173,8 @@ public:
|
||||
break;
|
||||
case Data: makeCall(that, &Self::data, r, args);
|
||||
break;
|
||||
case ItemData: makeCall(that, &Self::itemData, r, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,6 +185,8 @@ public:
|
||||
break;
|
||||
case SetData: makeCall(that, &Self::setData, r, args);
|
||||
break;
|
||||
case SetItemData: makeCall(that, &Self::setItemData, r, args);
|
||||
break;
|
||||
case ClearItemData: makeCall(that, &Self::clearItemData, r, args);
|
||||
break;
|
||||
case InsertColumns: makeCall(that, &Self::insertColumns, r, args);
|
||||
@ -283,6 +290,50 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<int, QVariant> itemData(const QModelIndex &index) const
|
||||
{
|
||||
QMap<int, QVariant> result;
|
||||
bool tried = false;
|
||||
const auto readItemData = [this, &result, &tried](auto &&value){
|
||||
Q_UNUSED(this);
|
||||
using value_type = q20::remove_cvref_t<decltype(value)>;
|
||||
using multi_role = QGenericItemModelDetails::is_multi_role<value_type>;
|
||||
if constexpr (multi_role()) {
|
||||
tried = true;
|
||||
if constexpr (std::is_convertible_v<value_type, decltype(result)>) {
|
||||
result = value;
|
||||
} else {
|
||||
for (auto it = std::cbegin(value); it != std::cend(value); ++it) {
|
||||
int role = [this, key = QGenericItemModelDetails::key(it)]() {
|
||||
Q_UNUSED(this);
|
||||
if constexpr (multi_role::int_key)
|
||||
return int(key);
|
||||
else
|
||||
return roleNames().key(key.toUtf8(), -1);
|
||||
}();
|
||||
|
||||
if (role != -1)
|
||||
result.insert(role, QGenericItemModelDetails::value(it));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (index.isValid()) {
|
||||
const_row_reference row = rowData(index);
|
||||
if constexpr (dynamicColumns())
|
||||
readItemData(*std::next(std::cbegin(row), index.column()));
|
||||
else if constexpr (static_column_count == 0)
|
||||
readItemData(row);
|
||||
else if (QGenericItemModelDetails::isValid(row))
|
||||
for_element_at(row, index.column(), readItemData);
|
||||
|
||||
if (!tried) // no multi-role item found
|
||||
return m_itemModel->QAbstractItemModel::itemData(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &data, int role)
|
||||
{
|
||||
if (!index.isValid())
|
||||
@ -342,6 +393,78 @@ public:
|
||||
return success;
|
||||
}
|
||||
|
||||
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &data)
|
||||
{
|
||||
if (!index.isValid() || data.isEmpty())
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
if constexpr (isMutable()) {
|
||||
auto emitDataChanged = qScopeGuard([&success, this, &index, &data]{
|
||||
if (success)
|
||||
Q_EMIT dataChanged(index, index, data.keys());
|
||||
});
|
||||
|
||||
bool tried = false;
|
||||
auto writeItemData = [this, &tried, &data](auto &target) -> bool {
|
||||
Q_UNUSED(this);
|
||||
using value_type = q20::remove_cvref_t<decltype(target)>;
|
||||
using multi_role = QGenericItemModelDetails::is_multi_role<value_type>;
|
||||
if constexpr (multi_role()) {
|
||||
using key_type = typename value_type::key_type;
|
||||
tried = true;
|
||||
const auto roleName = [map = roleNames()](int role) { return map.value(role); };
|
||||
|
||||
// transactional: only update target if all values from data
|
||||
// can be stored. Storing never fails with int-keys.
|
||||
if constexpr (!multi_role::int_key)
|
||||
{
|
||||
auto invalid = std::find_if(data.keyBegin(), data.keyEnd(),
|
||||
[&roleName](int role) { return roleName(role).isEmpty(); }
|
||||
);
|
||||
|
||||
if (invalid != data.keyEnd()) {
|
||||
qWarning("No role name set for %d", *invalid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &&[role, value] : data.asKeyValueRange()) {
|
||||
if constexpr (multi_role::int_key)
|
||||
target[static_cast<key_type>(role)] = value;
|
||||
else
|
||||
target[QString::fromUtf8(roleName(role))] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
row_reference row = rowData(index);
|
||||
if constexpr (dynamicColumns()) {
|
||||
success = writeItemData(*std::next(std::begin(row), index.column()));
|
||||
} else if constexpr (static_column_count == 0) {
|
||||
success = writeItemData(row);
|
||||
} else if (QGenericItemModelDetails::isValid(row)) {
|
||||
for_element_at(row, index.column(), [&writeItemData, &success](auto &&target){
|
||||
using target_type = decltype(target);
|
||||
// we can only assign to an lvalue reference
|
||||
if constexpr (std::is_lvalue_reference_v<target_type>
|
||||
&& !std::is_const_v<std::remove_reference_t<target_type>>) {
|
||||
success = writeItemData(std::forward<target_type>(target));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!tried) {
|
||||
// setItemData will emit the dataChanged signal
|
||||
emitDataChanged.dismiss();
|
||||
success = m_itemModel->QAbstractItemModel::setItemData(index, data);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool clearItemData(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid())
|
||||
|
@ -295,11 +295,13 @@ public:
|
||||
Flags,
|
||||
HeaderData,
|
||||
Data,
|
||||
ItemData,
|
||||
};
|
||||
|
||||
enum Op {
|
||||
Destroy,
|
||||
SetData,
|
||||
SetItemData,
|
||||
ClearItemData,
|
||||
InsertColumns,
|
||||
RemoveColumns,
|
||||
|
@ -84,6 +84,10 @@ private slots:
|
||||
void data();
|
||||
void setData_data() { createTestData(); }
|
||||
void setData();
|
||||
void itemData_data() { createTestData(); }
|
||||
void itemData();
|
||||
void setItemData_data() { createTestData(); }
|
||||
void setItemData();
|
||||
void clearItemData_data() { createTestData(); }
|
||||
void clearItemData();
|
||||
void insertRows_data() { createTestData(); }
|
||||
@ -183,6 +187,18 @@ private:
|
||||
{{{Qt::DisplayRole, "DISPLAY2"}, {Qt::DecorationRole, "DECORATION2"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY3"}, {Qt::DecorationRole, "DECORATION3"}}},
|
||||
};
|
||||
std::vector<std::vector<QMap<int, QVariant>>> tableOfIntRoles = {
|
||||
{{{Qt::DisplayRole, "DISPLAY0"}, {Qt::DecorationRole, "DECORATION0"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY1"}, {Qt::DecorationRole, "DECORATION1"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY2"}, {Qt::DecorationRole, "DECORATION2"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY3"}, {Qt::DecorationRole, "DECORATION3"}}},
|
||||
};
|
||||
std::vector<std::vector<std::map<int, QVariant>>> stdTableOfIntRoles = {
|
||||
{{{Qt::DisplayRole, "DISPLAY0"}, {Qt::DecorationRole, "DECORATION0"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY1"}, {Qt::DecorationRole, "DECORATION1"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY2"}, {Qt::DecorationRole, "DECORATION2"}}},
|
||||
{{{Qt::DisplayRole, "DISPLAY3"}, {Qt::DecorationRole, "DECORATION3"}}},
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<Data> m_data;
|
||||
@ -198,7 +214,8 @@ public:
|
||||
RemoveColumns = 0x08,
|
||||
ChangeColumns = InsertColumns | RemoveColumns,
|
||||
SetData = 0x10,
|
||||
All = ChangeRows | ChangeColumns | SetData
|
||||
All = ChangeRows | ChangeColumns | SetData,
|
||||
SetItemData = 0x20,
|
||||
};
|
||||
Q_DECLARE_FLAGS(ChangeActions, ChangeAction);
|
||||
};
|
||||
@ -288,13 +305,21 @@ void tst_QGenericItemModel::createTestData()
|
||||
<< 5 << ChangeActions(ChangeAction::ReadOnly);
|
||||
|
||||
ADD_COPY(listOfNamedRoles)
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData);
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData);
|
||||
ADD_POINTER(listOfNamedRoles)
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData);
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData);
|
||||
ADD_COPY(tableOfEnumRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All);
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
ADD_POINTER(tableOfEnumRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All);
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
ADD_COPY(tableOfIntRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
ADD_POINTER(tableOfIntRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
ADD_COPY(stdTableOfIntRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
ADD_POINTER(stdTableOfIntRoles)
|
||||
<< 1 << ChangeActions(ChangeAction::All | ChangeAction::SetItemData);
|
||||
|
||||
#undef ADD_COPY
|
||||
#undef ADD_POINTER
|
||||
@ -459,6 +484,70 @@ void tst_QGenericItemModel::setData()
|
||||
QCOMPARE(first.data() == oldValue, !changeActions.testFlag(ChangeAction::SetData));
|
||||
}
|
||||
|
||||
void tst_QGenericItemModel::itemData()
|
||||
{
|
||||
QFETCH(Factory, factory);
|
||||
auto model = factory();
|
||||
|
||||
QVERIFY(model->itemData({}).isEmpty());
|
||||
|
||||
const QModelIndex index = model->index(0, 0);
|
||||
const QMap<int, QVariant> itemData = model->itemData(index);
|
||||
for (int role = 0; role < Qt::UserRole; ++role)
|
||||
QCOMPARE(itemData.value(role), index.data(role));
|
||||
}
|
||||
|
||||
void tst_QGenericItemModel::setItemData()
|
||||
{
|
||||
QFETCH(Factory, factory);
|
||||
auto model = factory();
|
||||
QFETCH(const ChangeActions, changeActions);
|
||||
|
||||
QVERIFY(!model->setItemData({}, {}));
|
||||
|
||||
const QModelIndex index = model->index(0, 0);
|
||||
QMap<int, QVariant> itemData = model->itemData(index);
|
||||
// we only care about multi-role models
|
||||
if (itemData.keys() == QList<int>{Qt::DisplayRole, Qt::EditRole})
|
||||
QSKIP("Can't test setItemData on models with single values!");
|
||||
|
||||
itemData = {};
|
||||
|
||||
const auto roles = model->roleNames().keys();
|
||||
for (int role : roles) {
|
||||
QVariant data = QStringLiteral("Role %1").arg(role);
|
||||
itemData.insert(role, data);
|
||||
}
|
||||
|
||||
QCOMPARE_NE(model->itemData(index), itemData);
|
||||
QCOMPARE(model->setItemData(index, itemData),
|
||||
changeActions.testFlag(ChangeAction::SetItemData));
|
||||
if (!changeActions.testFlag(ChangeAction::SetItemData))
|
||||
return; // nothing more to test for those models
|
||||
|
||||
{
|
||||
const auto newItemData = model->itemData(index);
|
||||
auto diagnostics = qScopeGuard([&]{
|
||||
qDebug() << "Mismatch";
|
||||
qDebug() << " Actual:" << newItemData;
|
||||
qDebug() << " Expected:" << itemData;
|
||||
});
|
||||
QCOMPARE(newItemData == itemData, changeActions.testFlag(ChangeAction::SetItemData));
|
||||
diagnostics.dismiss();
|
||||
}
|
||||
|
||||
for (int role = 0; role < Qt::UserRole; ++role) {
|
||||
QVariant data = index.data(role);
|
||||
auto diagnostics = qScopeGuard([&]{
|
||||
qDebug() << "Mismatch for" << Qt::ItemDataRole(role);
|
||||
qDebug() << " Actual:" << data;
|
||||
qDebug() << " Expected:" << itemData.value(role);
|
||||
});
|
||||
QCOMPARE(data == itemData.value(role), changeActions.testFlag(ChangeAction::SetData));
|
||||
diagnostics.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QGenericItemModel::clearItemData()
|
||||
{
|
||||
QFETCH(Factory, factory);
|
||||
@ -495,6 +584,10 @@ void tst_QGenericItemModel::insertRows()
|
||||
QEXPECT_FAIL("listOfNamedRolesCopy", "QVariantMap is empty by design", Continue);
|
||||
QEXPECT_FAIL("tableOfEnumRolesPointer", "QVariantMap is empty by design", Continue);
|
||||
QEXPECT_FAIL("tableOfEnumRolesCopy", "QVariantMap is empty by design", Continue);
|
||||
QEXPECT_FAIL("tableOfIntRolesPointer", "QVariantMap is empty by design", Continue);
|
||||
QEXPECT_FAIL("tableOfIntRolesCopy", "QVariantMap is empty by design", Continue);
|
||||
QEXPECT_FAIL("stdTableOfIntRolesPointer", "std::map is empty by design", Continue);
|
||||
QEXPECT_FAIL("stdTableOfIntRolesCopy", "std::map is empty by design", Continue);
|
||||
};
|
||||
// get and put data into the new row
|
||||
const QModelIndex firstItem = model->index(0, 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user