QGIM: add MultiColumn wrapper for disambiguation
If a type has both a metaobject and implements the tuple protocol, then we need to disambiguate as row_traits would be ambiguous. Add a wrapper that holds the type and provides a forwarding tuple protocol implementation. With either or the SingleColumn wrapper, client code can decide whether the type should be a a multi-column row in a table (accessed via tuple protocol), or a multi-role item in a list (accessed via the meta object). Add documentation. Change-Id: I0145f7a35a125a138254397bc7eafa5506e5e3ad Reviewed-by: Artem Dyomin <artem.dyomin@qt.io>
This commit is contained in:
parent
5901f0ba09
commit
75db2305cb
@ -172,9 +172,79 @@ QT_BEGIN_NAMESPACE
|
||||
\note The implementation of \c{get} above requires C++23. A C++17 compliant
|
||||
implementation can be found in the unit test code for QGenericItemModel.
|
||||
|
||||
Types that have a meta objects, and implement the C++ tuple protocol, also
|
||||
can cause compile-time ambiguity when used as the row type, as the framework
|
||||
won't know which API to use to access the individual values. Use the
|
||||
QGenericItemModel::SingleColumn and QGenericItemModel::MultiColumns wrapper
|
||||
to disambiguate.
|
||||
|
||||
\sa {Model/View Programming}
|
||||
*/
|
||||
|
||||
/*!
|
||||
\typedef QGenericItemModel::SingleColumn
|
||||
|
||||
Use this type to disambiguate when using the type \c{T} as the row type in
|
||||
the range. If \c{T} provides a metaobject, then the framework will by
|
||||
default represent the type as multiple columns, resulting in a table model.
|
||||
|
||||
\snippet qgenericitemmodel/main.cpp color_gadget_0
|
||||
|
||||
When stored in a sequential range, this type will be interpreted as
|
||||
multi-column rows with each property being one column. The range will be
|
||||
represented as a table.
|
||||
|
||||
\code
|
||||
QList<ColorEntry> colors = {
|
||||
// ...
|
||||
};
|
||||
QGenericItemModel tableModel(colors); // columnCount() == 3
|
||||
\endcode
|
||||
|
||||
When wrapped into QGenericItemModel::SingleColumn, the model will be a list,
|
||||
with each instance of \c{T} represented as an item with multiple roles.
|
||||
|
||||
\code
|
||||
QList<QGenericItemModel::SingleColumn<ColorEntry>> colors = {
|
||||
// ...
|
||||
};
|
||||
QGenericItemModel listModel(colors); // columnCount() == 1
|
||||
\endcode
|
||||
|
||||
\sa QGenericItemModel::MultiColumn
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QGenericItemModel::MultiColumn
|
||||
\brief Represents the wrapped type \c{T} as multiple columns in a QGenericItemModel.
|
||||
\inmodule QtCore
|
||||
\ingroup model-view
|
||||
\since 6.10
|
||||
|
||||
Use this type to disambiguate when the type \c{T} has both a metaobject, and
|
||||
implements \l{the C++ tuple protocol}. The type will be represented as
|
||||
multiple columns, and the individual values will be accessed through the
|
||||
tuple protocol.
|
||||
|
||||
\snippet qgenericitemmodel/main.cpp color_gadget_0
|
||||
\code
|
||||
namespace std {
|
||||
template <> struct tuple_size<ColorEntry> : integral_constant<size_t, 3> {};
|
||||
// ...
|
||||
}
|
||||
|
||||
QList<QGenericItemModel::MultiColumn<ColorEntry>> colors = {
|
||||
// ...
|
||||
};
|
||||
QGenericItemModel colorList(colors);
|
||||
\endcode
|
||||
|
||||
To represent the type a single column value with multiple roles, use
|
||||
QGenericItemModel::SingleColumn instead.
|
||||
|
||||
\sa QGenericItemModel::SingleColumn
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Range, QGenericItemModelDetails::if_is_range<Range>> QGenericItemModel::QGenericItemModel(Range &&range, QObject *parent)
|
||||
|
||||
|
@ -16,6 +16,32 @@ public:
|
||||
template <typename T>
|
||||
using SingleColumn = std::tuple<T>;
|
||||
|
||||
template <typename T>
|
||||
struct MultiColumn
|
||||
{
|
||||
using type = std::remove_pointer_t<T>;
|
||||
T data{};
|
||||
template <typename X>
|
||||
using if_get_matches = std::enable_if_t<std::is_same_v<q20::remove_cvref_t<X>,
|
||||
MultiColumn<T>>, bool>;
|
||||
|
||||
template <typename V = T,
|
||||
std::enable_if_t<std::is_constructible_v<bool, V>, bool> = true>
|
||||
constexpr explicit operator bool() const noexcept { return bool(data); }
|
||||
|
||||
// unconstrained on size_t I, gcc internal error #3280
|
||||
template <std::size_t I, typename V, if_get_matches<V> = true>
|
||||
friend inline decltype(auto) get(V &&multiColumn)
|
||||
{
|
||||
static_assert(I < std::tuple_size_v<type>, "Index out of bounds for wrapped type");
|
||||
Q_ASSERT(multiColumn);
|
||||
if constexpr (std::is_pointer_v<T>)
|
||||
return get<I>(*multiColumn.data);
|
||||
else
|
||||
return get<I>(q23::forward_like<V>(multiColumn.data));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Range,
|
||||
QGenericItemModelDetails::if_is_range<Range> = true>
|
||||
explicit QGenericItemModel(Range &&range, QObject *parent = nullptr);
|
||||
@ -1057,4 +1083,15 @@ QGenericItemModel::QGenericItemModel(Range &&range, QObject *parent)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace std {
|
||||
template <typename T>
|
||||
struct tuple_size<QT_PREPEND_NAMESPACE(QGenericItemModel)::MultiColumn<T>>
|
||||
: tuple_size<typename QT_PREPEND_NAMESPACE(QGenericItemModel)::MultiColumn<T>::type>
|
||||
{};
|
||||
template <std::size_t I, typename T>
|
||||
struct tuple_element<I, QT_PREPEND_NAMESPACE(QGenericItemModel)::MultiColumn<T>>
|
||||
: tuple_element<I, typename QT_PREPEND_NAMESPACE(QGenericItemModel)::MultiColumn<T>::type>
|
||||
{};
|
||||
}
|
||||
|
||||
#endif // QGENERICITEMMODEL_H
|
||||
|
@ -198,7 +198,7 @@ namespace QGenericItemModelDetails
|
||||
template <typename T> static auto pointerTo(const T &&t) = delete;
|
||||
|
||||
template <typename T>
|
||||
static bool isValid(T &&t)
|
||||
static constexpr bool isValid(T &&t) noexcept
|
||||
{
|
||||
if constexpr (std::is_constructible_v<bool, T>)
|
||||
return bool(t);
|
||||
|
@ -65,6 +65,38 @@ private:
|
||||
int m_number = 42;
|
||||
};
|
||||
|
||||
// a class that can be both and requires disambiguation
|
||||
class MetaObjectTuple : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString display MEMBER m_string)
|
||||
Q_PROPERTY(int number MEMBER m_number)
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
private:
|
||||
QString m_string = "4321";
|
||||
int m_number = 24;
|
||||
|
||||
template <size_t I, typename G,
|
||||
std::enable_if_t<(I < 2), bool> = true,
|
||||
std::enable_if_t<std::is_same_v<q20::remove_cvref_t<G>, MetaObjectTuple>, bool> = true
|
||||
>
|
||||
friend inline decltype(auto) get(G &&item)
|
||||
{
|
||||
if constexpr (I == 0)
|
||||
return q23::forward_like<G>(item.m_string);
|
||||
else if constexpr (I == 1)
|
||||
return q23::forward_like<G>(item.m_number);
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <> struct tuple_size<MetaObjectTuple> : std::integral_constant<size_t, 2> {};
|
||||
template <> struct tuple_element<0, MetaObjectTuple> { using type = QString; };
|
||||
template <> struct tuple_element<1, MetaObjectTuple> { using type = int; };
|
||||
}
|
||||
|
||||
struct Row
|
||||
{
|
||||
Item m_item;
|
||||
@ -200,6 +232,23 @@ private:
|
||||
&row1, &row2, &row3
|
||||
};
|
||||
|
||||
MetaObjectTuple mot1;
|
||||
MetaObjectTuple mot2;
|
||||
MetaObjectTuple mot3;
|
||||
std::vector<QGenericItemModel::SingleColumn<MetaObjectTuple *>> listOfMetaObjectTuple = {
|
||||
&mot1,
|
||||
&mot2,
|
||||
&mot3,
|
||||
};
|
||||
MetaObjectTuple mot4;
|
||||
MetaObjectTuple mot5;
|
||||
MetaObjectTuple mot6;
|
||||
std::vector<QGenericItemModel::MultiColumn<MetaObjectTuple *>> tableOfMetaObjectTuple = {
|
||||
{&mot4},
|
||||
{&mot5},
|
||||
{&mot6},
|
||||
};
|
||||
|
||||
// bad (but legal) get() overload that never returns a mutable reference
|
||||
std::vector<ConstRow> vectorOfConstStructs = {
|
||||
{"one"},
|
||||
@ -364,6 +413,10 @@ void tst_QGenericItemModel::createTestData()
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData);
|
||||
ADD_COPY(listOfObjects)
|
||||
<< 2 << (ChangeAction::ChangeRows | ChangeAction::SetData);
|
||||
ADD_COPY(listOfMetaObjectTuple)
|
||||
<< 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData);
|
||||
ADD_COPY(tableOfMetaObjectTuple)
|
||||
<< 2 << (ChangeAction::ChangeRows | ChangeAction::SetData);
|
||||
|
||||
ADD_COPY(tableOfNumbers)
|
||||
<< 5 << ChangeActions(ChangeAction::All);
|
||||
@ -694,6 +747,8 @@ void tst_QGenericItemModel::insertRows()
|
||||
QEXPECT_FAIL("tableOfPointersPointer", "No item created", Continue);
|
||||
QEXPECT_FAIL("tableOfRowPointersPointer", "No row created", Continue);
|
||||
QEXPECT_FAIL("listOfObjectsCopy", "No object created", Continue);
|
||||
QEXPECT_FAIL("listOfMetaObjectTupleCopy", "No object created", Continue);
|
||||
QEXPECT_FAIL("tableOfMetaObjectTupleCopy", "No object created", Continue);
|
||||
|
||||
// associative containers are default constructed with no valid data
|
||||
ignoreFailureFromAssociativeContainers();
|
||||
|
Loading…
x
Reference in New Issue
Block a user