diff options
-rw-r--r-- | src/corelib/doc/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp | 45 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel.cpp | 298 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel.h | 110 | ||||
-rw-r--r-- | src/gui/itemmodels/qstandarditemmodel.cpp | 39 | ||||
-rw-r--r-- | src/gui/itemmodels/qstandarditemmodel.h | 2 | ||||
-rw-r--r-- | src/widgets/itemviews/qstyleditemdelegate.cpp | 66 | ||||
-rw-r--r-- | tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp | 141 |
7 files changed, 672 insertions, 29 deletions
diff --git a/src/corelib/doc/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp b/src/corelib/doc/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp index 2e055ec439..397f6f72af 100644 --- a/src/corelib/doc/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp @@ -141,3 +141,48 @@ private: }; //! [12] +//! [13] +QVariant text = model->data(index, Qt::DisplayRole); +QVariant decoration = model->data(index, Qt::DecorationRole); +QVariant checkState = model->data(index, Qt::CheckStateRole); +// etc. +//! [13] + +//! [14] +std::array<QModelRoleData, 3> roleData = { { + QModelRoleData(Qt::DisplayRole), + QModelRoleData(Qt::DecorationRole), + QModelRoleData(Qt::CheckStateRole) +} }; + +// Usually, this is not necessary: A QModelRoleDataSpan +// will be built automatically for you when passing an array-like +// container to multiData(). +QModelRoleDataSpan span(roleData); + +model->multiData(index, span); + +// Use roleData[0].data(), roleData[1].data(), etc. +//! [14] + +//! [15] +void MyModel::multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const +{ + for (QModelRoleData &roleData : roleDataSpan) { + int role = roleData.role(); + + // ... obtain the data for index and role ... + + roleData.setData(result); + } +} +//! [15] + +//! [16] +QVariant MyModel::data(const QModelIndex &index, int role) const +{ + QModelRoleData roleData(role); + multiData(index, roleData); + return roleData.data(); +} +//! [16] diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp index a686e918c3..8314ea8958 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.cpp +++ b/src/corelib/itemmodels/qabstractitemmodel.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -91,6 +92,226 @@ void QPersistentModelIndexData::destroy(QPersistentModelIndexData *data) } /*! + \class QModelRoleData + \inmodule QtCore + \since 6.0 + \ingroup model-view + \brief The QModelRoleData class holds a role and the data associated to that role. + + QModelRoleData objects store an item role (which is a value from the + Qt::ItemDataRole enumeration, or an arbitrary integer for a custom role) + as well as the data associated with that role. + + A QModelRoleData object is typically created by views or delegates, + setting which role they want to fetch the data for. The object + is then passed to models (see QAbstractItemModel::multiData()), + which populate the data corresponding to the role stored. Finally, + the view visualizes the data retrieved from the model. + + \sa {Model/View Programming}, QModelRoleDataSpan +*/ + +/*! + QModelRoleData::QModelRoleData(int role) noexcept + + Constructs a QModelRoleData object for the given \a role. + + \sa Qt::ItemDataRole +*/ + +/*! + int QModelRoleData::role() const noexcept + + Returns the role held by this object. + + \sa Qt::ItemDataRole +*/ + +/*! + const QVariant &QModelRoleData::data() const noexcept + + Returns the data held by this object. + + \sa setData() +*/ + +/*! + QVariant &QModelRoleData::data() noexcept + + Returns the data held by this object as a modifiable reference. + + \sa setData() +*/ + +/*! + template <typename T> void QModelRoleData::setData(T &&value) + + Sets the data held by this object to \a value. + \a value must be of a datatype which can be stored in a QVariant. + + \sa data(), clearData(), Q_DECLARE_METATYPE +*/ + +/*! + void QModelRoleData::clearData() noexcept + + Clears the data held by this object. Note that the role is + unchanged; only the data is cleared. + + \sa data() +*/ + +/*! + \class QModelRoleDataSpan + \inmodule QtCore + \since 6.0 + \ingroup model-view + \brief The QModelRoleDataSpan class provides a span over QModelRoleData objects. + + A QModelRoleDataSpan is used as an abstraction over an array of + QModelRoleData objects. + + Like a view, QModelRoleDataSpan provides a small object (pointer + and size) that can be passed to functions that need to examine the + contents of the array. A QModelRoleDataSpan can be constructed from + any array-like sequence (plain arrays, QVector, std::vector, + QVarLengthArray, and so on). Moreover, it does not own the + sequence, which must therefore be kept alive longer than any + QModelRoleDataSpan objects referencing it. + + Unlike a view, QModelRoleDataSpan is a span, so it allows for + modifications to the underlying elements. + + QModelRoleDataSpan's main use case is making it possible + for a model to return the data corresponding to different roles + in one call. + + In order to draw one element from a model, a view (through its + delegates) will generally request multiple roles for the same index + by calling \c{data()} as many times as needed: + + \snippet code/src_corelib_kernel_qabstractitemmodel.cpp 13 + + QModelRoleDataSpan allows a view to request the same data + using just one function call. + + This is achieved by having the view prepare a suitable array of + QModelRoleData objects, each initialized with the role that should + be fetched. The array is then wrapped in a QModelRoleDataSpan + object, which is then passed to a model's \c{multiData()} function. + + \snippet code/src_corelib_kernel_qabstractitemmodel.cpp 14 + + Views are encouraged to store the array of QModelRoleData objects + (and, possibly, the corresponding span) and re-use it in subsequent + calls to the model. This allows to reduce the memory allocations + related with creating and returning QVariant objects. + + Finally, given a QModelRoleDataSpan object, the model's + responsibility is to fill in the data corresponding to each role in + the span. How this is done depends on the concrete model class. + Here's a sketch of a possible implementation that iterates over the + span and uses \c{setData()} on each element: + + \snippet code/src_corelib_kernel_qabstractitemmodel.cpp 15 + + \sa {Model/View Programming}, QAbstractItemModel::multiData() +*/ + +/*! + QModelRoleDataSpan::QModelRoleDataSpan() noexcept + + Constructs an empty QModelRoleDataSpan. Its data() will be set to + \nullptr, and its length to zero. +*/ + +/*! + QModelRoleDataSpan::QModelRoleDataSpan(QModelRoleData &modelRoleData) noexcept + + Constructs an QModelRoleDataSpan spanning over \a modelRoleData, + seen as a 1-element array. +*/ + +/*! + QModelRoleDataSpan::QModelRoleDataSpan(QModelRoleData *modelRoleData, qsizetype len) + + Constructs an QModelRoleDataSpan spanning over the array beginning + at \a modelRoleData and with length \a len. + + \note The array must be kept alive as long as this object has not + been destructed. +*/ + +/*! + template <typename Container> QModelRoleDataSpan::QModelRoleDataSpan(Container &c) noexcept + + Constructs an QModelRoleDataSpan spanning over the container \a c, + which can be any contiguous container of QModelRoleData objects. + For instance, it can be a \c{QVector<QModelRoleData>}, + a \c{std::array<QModelRoleData, 10>} and so on. + + \note The container must be kept alive as long as this object has not + been destructed. +*/ + +/*! + qsizetype QModelRoleDataSpan::size() const noexcept + + Returns the length of the span represented by this object. +*/ + +/*! + qsizetype QModelRoleDataSpan::length() const noexcept + + Returns the length of the span represented by this object. +*/ + +/*! + QModelRoleData *QModelRoleDataSpan::data() const noexcept + + Returns a pointer to the beginning of the span represented by this + object. +*/ + +/*! + QModelRoleData *QModelRoleDataSpan::begin() const noexcept + + Returns a pointer to the beginning of the span represented by this + object. +*/ + +/*! + QModelRoleData *QModelRoleDataSpan::end() const noexcept + + Returns a pointer to the imaginary element one past the end of the + span represented by this object. +*/ + +/*! + QModelRoleData &QModelRoleDataSpan::operator[](qsizetype index) const + + Returns a modifiable reference to the QModelRoleData at position + \a index in the span. + + \note \a index must be a valid index for this span (0 <= \a index < size()). +*/ + +/*! + const QVariant *QModelRoleDataSpan::dataForRole(int role) const + + Returns the data associated with the first QModelRoleData in the + span that has its role equal to \a role. If such a QModelRoleData + object does not exist, the behavior is undefined. + + \note Avoid calling this function from the model's side, as a + model cannot possibly know in advance which roles are in a given + QModelRoleDataSpan. This function is instead suitable for views and + delegates, which have control over the roles in the span. + + \sa QModelRoleData::data() +*/ + +/*! \class QPersistentModelIndex \inmodule QtCore \ingroup shared @@ -423,6 +644,20 @@ QVariant QPersistentModelIndex::data(int role) const return QVariant(); } + +/*! + Populates the given \a roleDataSpan for the item referred to by the + index. + + \since 6.0 + \sa Qt::ItemDataRole, QAbstractItemModel::setData() +*/ +void QPersistentModelIndex::multiData(QModelRoleDataSpan roleDataSpan) const +{ + if (d) + d->index.multiData(roleDataSpan); +} + /*! \since 4.2 @@ -1109,6 +1344,14 @@ void QAbstractItemModel::resetInternalData() */ /*! + \fn void QModelIndex::multiData(QModelRoleDataSpan roleDataSpan) const + \since 6.0 + + Populates the given \a roleDataSpan for the item referred to by the + index. +*/ + +/*! \fn Qt::ItemFlags QModelIndex::flags() const \since 4.2 @@ -3401,6 +3644,61 @@ bool QAbstractItemModel::checkIndex(const QModelIndex &index, CheckIndexOptions } /*! + \since 6.0 + + Fills the \a roleDataSpan with the requested data for the given \a index. + + The default implementation will call simply data() for each role in + the span. A subclass can reimplement this function to provide data + to views more efficiently: + + \snippet code/src_corelib_kernel_qabstractitemmodel.cpp 15 + + In the snippet above, \c{index} is the same for the entire call. + This means that accessing to the necessary data structures in order + to retrieve the information for \c{index} can be done only once + (hoisting the relevant code out of the loop). + + The usage of QModelRoleData::setData(), or similarly + QVariant::setValue(), is encouraged over constructing a QVariant + separately and using a plain assignment operator; this is + because the former allow to re-use the memory already allocated for + the QVariant object stored inside a QModelRoleData, while the latter + always allocates the new variant and then destroys the old one. + + Note that views may call multiData() with spans that have been used + in previous calls, and therefore may already contain some data. + Therefore, it is imperative that if the model cannot return the + data for a given role, then it must clear the data in the + corresponding QModelRoleData object. This can be done by calling + QModelRoleData::clearData(), or similarly by setting a default + constructed QVariant, and so on. Failure to clear the data will + result in the view believing that the "old" data is meant to be + used for the corresponding role. + + Finally, in order to avoid code duplication, a subclass may also + decide to reimplement data() in terms of multiData(), by supplying + a span of just one element: + + \snippet code/src_corelib_kernel_qabstractitemmodel.cpp 16 + + \note Models are not allowed to modify the roles in the span, or + to rearrange the span elements. Doing so results in undefined + behavior. + + \note It is illegal to pass an invalid model index to this function. + + \sa QModelRoleDataSpan, data() +*/ +void QAbstractItemModel::multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const +{ + Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid)); + + for (QModelRoleData &d : roleDataSpan) + d.setData(data(index, d.role())); +} + +/*! \class QAbstractTableModel \inmodule QtCore \brief The QAbstractTableModel class provides an abstract model that can be diff --git a/src/corelib/itemmodels/qabstractitemmodel.h b/src/corelib/itemmodels/qabstractitemmodel.h index 325ff1df4a..9cda91e66e 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.h +++ b/src/corelib/itemmodels/qabstractitemmodel.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -49,6 +50,108 @@ QT_REQUIRE_CONFIG(itemmodel); QT_BEGIN_NAMESPACE +class QModelRoleData +{ + int m_role; + QVariant m_data; + +public: + explicit QModelRoleData(int role) noexcept + : m_role(role) + {} + + constexpr int role() const noexcept { return m_role; } + constexpr QVariant &data() noexcept { return m_data; } + constexpr const QVariant &data() const noexcept { return m_data; } + + template <typename T> + constexpr void setData(T &&value) noexcept(noexcept(m_data.setValue(std::forward<T>(value)))) + { m_data.setValue(std::forward<T>(value)); } + + void clearData() noexcept { m_data.clear(); } +}; + +Q_DECLARE_TYPEINFO(QModelRoleData, Q_MOVABLE_TYPE); + +class QModelRoleDataSpan; + +namespace QtPrivate { +template <typename T, typename Enable = void> +struct IsContainerCompatibleWithModelRoleDataSpan : std::false_type {}; + +template <typename T> +struct IsContainerCompatibleWithModelRoleDataSpan<T, std::enable_if_t<std::conjunction_v< + // lacking concepts and ranges, we accept any T whose std::data yields a suitable pointer ... + std::is_convertible<decltype( std::data(std::declval<T &>()) ), QModelRoleData *>, + // ... and that has a suitable size ... + std::is_convertible<decltype( std::size(std::declval<T &>()) ), qsizetype>, + // ... and it's a range as it defines an iterator-like API + std::is_convertible< + typename std::iterator_traits<decltype( std::begin(std::declval<T &>()) )>::value_type, + QModelRoleData + >, + std::is_convertible< + decltype( std::begin(std::declval<T &>()) != std::end(std::declval<T &>()) ), + bool>, + // Don't make an accidental copy constructor + std::negation<std::is_same<std::decay_t<T>, QModelRoleDataSpan>> + >>> : std::true_type {}; +} // namespace QtPrivate + +class QModelRoleDataSpan +{ + QModelRoleData *m_modelRoleData = nullptr; + qsizetype m_len = 0; + + template <typename T> + using if_compatible_container = std::enable_if_t<QtPrivate::IsContainerCompatibleWithModelRoleDataSpan<T>::value, bool>; + +public: + constexpr QModelRoleDataSpan() noexcept {} + + constexpr QModelRoleDataSpan(QModelRoleData &modelRoleData) noexcept + : m_modelRoleData(&modelRoleData), + m_len(1) + {} + + constexpr QModelRoleDataSpan(QModelRoleData *modelRoleData, qsizetype len) + : m_modelRoleData(modelRoleData), + m_len(len) + {} + + template <typename Container, if_compatible_container<Container> = true> + constexpr QModelRoleDataSpan(Container &c) noexcept(noexcept(std::data(c)) && noexcept(std::size(c))) + : m_modelRoleData(std::data(c)), + m_len(qsizetype(std::size(c))) + {} + + constexpr qsizetype size() const noexcept { return m_len; } + constexpr qsizetype length() const noexcept { return m_len; } + constexpr QModelRoleData *data() const noexcept { return m_modelRoleData; } + constexpr QModelRoleData *begin() const noexcept { return m_modelRoleData; } + constexpr QModelRoleData *end() const noexcept { return m_modelRoleData + m_len; } + constexpr QModelRoleData &operator[](qsizetype index) const { return m_modelRoleData[index]; } + + constexpr QVariant *dataForRole(int role) const + { +#ifdef __cpp_lib_constexpr_algorithms + auto result = std::find_if(begin(), end(), [role](const QModelRoleData &roleData) { + return roleData.role() == role; + }); +#else + auto result = begin(); + const auto e = end(); + for (; result != e; ++result) { + if (result->role() == role) + break; + } +#endif + + return Q_ASSERT(result != end()), &result->data(); + } +}; + +Q_DECLARE_TYPEINFO(QModelRoleDataSpan, Q_MOVABLE_TYPE); class QAbstractItemModel; class QPersistentModelIndex; @@ -69,6 +172,7 @@ public: inline QModelIndex siblingAtColumn(int column) const; inline QModelIndex siblingAtRow(int row) const; inline QVariant data(int role = Qt::DisplayRole) const; + inline void multiData(QModelRoleDataSpan roleDataSpan) const; inline Qt::ItemFlags flags() const; constexpr inline const QAbstractItemModel *model() const noexcept { return m; } constexpr inline bool isValid() const noexcept { return (r >= 0) && (c >= 0) && (m != nullptr); } @@ -132,6 +236,7 @@ public: QModelIndex parent() const; QModelIndex sibling(int row, int column) const; QVariant data(int role = Qt::DisplayRole) const; + void multiData(QModelRoleDataSpan roleDataSpan) const; Qt::ItemFlags flags() const; const QAbstractItemModel *model() const; bool isValid() const; @@ -256,6 +361,8 @@ public: Q_REQUIRED_RESULT bool checkIndex(const QModelIndex &index, CheckIndexOptions options = CheckIndexOption::NoOption) const; + virtual void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const; + Q_SIGNALS: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles = QList<int>()); @@ -421,6 +528,9 @@ inline QModelIndex QModelIndex::siblingAtRow(int arow) const inline QVariant QModelIndex::data(int arole) const { return m ? m->data(*this, arole) : QVariant(); } +inline void QModelIndex::multiData(QModelRoleDataSpan roleDataSpan) const +{ if (m) m->multiData(*this, roleDataSpan); } + inline Qt::ItemFlags QModelIndex::flags() const { return m ? m->flags(*this) : Qt::ItemFlags(); } diff --git a/src/gui/itemmodels/qstandarditemmodel.cpp b/src/gui/itemmodels/qstandarditemmodel.cpp index cf905eae66..1d3dc1303f 100644 --- a/src/gui/itemmodels/qstandarditemmodel.cpp +++ b/src/gui/itemmodels/qstandarditemmodel.cpp @@ -968,13 +968,31 @@ void QStandardItem::clearData() */ QVariant QStandardItem::data(int role) const { + QModelRoleData result(role); + multiData(result); + return result.data(); +} + +void QStandardItem::multiData(QModelRoleDataSpan roleDataSpan) const +{ Q_D(const QStandardItem); - const int r = (role == Qt::EditRole) ? Qt::DisplayRole : role; - for (const auto &value : d->values) { - if (value.role == r) - return value.value; + + const auto valuesBegin = d->values.begin(); + const auto valuesEnd = d->values.end(); + + for (auto &roleData : roleDataSpan) { + const int role = (roleData.role() == Qt::EditRole) ? Qt::DisplayRole : roleData.role(); + const auto hasSameRole = [role](const QStandardItemData &data) + { + return data.role == role; + }; + + auto dataIt = std::find_if(valuesBegin, valuesEnd, hasSameRole); + if (dataIt != valuesEnd) + roleData.setData(dataIt->value); + else + roleData.clearData(); } - return QVariant(); } /*! @@ -2826,6 +2844,17 @@ QVariant QStandardItemModel::data(const QModelIndex &index, int role) const /*! \reimp */ +void QStandardItemModel::multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(index); + if (item) + item->multiData(roleDataSpan); +} + +/*! + \reimp +*/ Qt::ItemFlags QStandardItemModel::flags(const QModelIndex &index) const { Q_D(const QStandardItemModel); diff --git a/src/gui/itemmodels/qstandarditemmodel.h b/src/gui/itemmodels/qstandarditemmodel.h index 3e0374d741..02274839cb 100644 --- a/src/gui/itemmodels/qstandarditemmodel.h +++ b/src/gui/itemmodels/qstandarditemmodel.h @@ -66,6 +66,7 @@ public: virtual ~QStandardItem(); virtual QVariant data(int role = Qt::UserRole + 1) const; + virtual void multiData(QModelRoleDataSpan roleDataSpan) const; virtual void setData(const QVariant &value, int role = Qt::UserRole + 1); void clearData(); @@ -331,6 +332,7 @@ public: bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool clearItemData(const QModelIndex &index) override; diff --git a/src/widgets/itemviews/qstyleditemdelegate.cpp b/src/widgets/itemviews/qstyleditemdelegate.cpp index 915a401a25..fb1d56464e 100644 --- a/src/widgets/itemviews/qstyleditemdelegate.cpp +++ b/src/widgets/itemviews/qstyleditemdelegate.cpp @@ -74,6 +74,7 @@ #include <qtableview.h> #endif +#include <array> #include <limits.h> QT_BEGIN_NAMESPACE @@ -96,6 +97,16 @@ public: } QItemEditorFactory *factory; + + mutable std::array<QModelRoleData, 7> modelRoleData = { + QModelRoleData(Qt::FontRole), + QModelRoleData(Qt::TextAlignmentRole), + QModelRoleData(Qt::ForegroundRole), + QModelRoleData(Qt::CheckStateRole), + QModelRoleData(Qt::DecorationRole), + QModelRoleData(Qt::DisplayRole), + QModelRoleData(Qt::BackgroundRole) + }; }; /*! @@ -276,33 +287,39 @@ QString QStyledItemDelegate::displayText(const QVariant &value, const QLocale& l void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const { - QVariant value = index.data(Qt::FontRole); - if (value.isValid() && !value.isNull()) { - option->font = qvariant_cast<QFont>(value).resolve(option->font); + option->index = index; + + Q_D(const QStyledItemDelegate); + QModelRoleDataSpan modelRoleDataSpan = d->modelRoleData; + index.multiData(modelRoleDataSpan); + + const QVariant *value; + value = modelRoleDataSpan.dataForRole(Qt::FontRole); + if (value->isValid() && !value->isNull()) { + option->font = qvariant_cast<QFont>(*value).resolve(option->font); option->fontMetrics = QFontMetrics(option->font); } - value = index.data(Qt::TextAlignmentRole); - if (value.isValid() && !value.isNull()) - option->displayAlignment = Qt::Alignment(value.toInt()); + value = modelRoleDataSpan.dataForRole(Qt::TextAlignmentRole); + if (value->isValid() && !value->isNull()) + option->displayAlignment = Qt::Alignment(value->toInt()); - value = index.data(Qt::ForegroundRole); - if (value.canConvert<QBrush>()) - option->palette.setBrush(QPalette::Text, qvariant_cast<QBrush>(value)); + value = modelRoleDataSpan.dataForRole(Qt::ForegroundRole); + if (value->canConvert<QBrush>()) + option->palette.setBrush(QPalette::Text, qvariant_cast<QBrush>(*value)); - option->index = index; - value = index.data(Qt::CheckStateRole); - if (value.isValid() && !value.isNull()) { + value = modelRoleDataSpan.dataForRole(Qt::CheckStateRole); + if (value->isValid() && !value->isNull()) { option->features |= QStyleOptionViewItem::HasCheckIndicator; - option->checkState = static_cast<Qt::CheckState>(value.toInt()); + option->checkState = static_cast<Qt::CheckState>(value->toInt()); } - value = index.data(Qt::DecorationRole); - if (value.isValid() && !value.isNull()) { + value = modelRoleDataSpan.dataForRole(Qt::DecorationRole); + if (value->isValid() && !value->isNull()) { option->features |= QStyleOptionViewItem::HasDecoration; - switch (value.userType()) { + switch (value->userType()) { case QMetaType::QIcon: { - option->icon = qvariant_cast<QIcon>(value); + option->icon = qvariant_cast<QIcon>(*value); QIcon::Mode mode; if (!(option->state & QStyle::State_Enabled)) mode = QIcon::Disabled; @@ -319,18 +336,18 @@ void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option, } case QMetaType::QColor: { QPixmap pixmap(option->decorationSize); - pixmap.fill(qvariant_cast<QColor>(value)); + pixmap.fill(qvariant_cast<QColor>(*value)); option->icon = QIcon(pixmap); break; } case QMetaType::QImage: { - QImage image = qvariant_cast<QImage>(value); + QImage image = qvariant_cast<QImage>(*value); option->icon = QIcon(QPixmap::fromImage(image)); option->decorationSize = image.size() / image.devicePixelRatio(); break; } case QMetaType::QPixmap: { - QPixmap pixmap = qvariant_cast<QPixmap>(value); + QPixmap pixmap = qvariant_cast<QPixmap>(*value); option->icon = QIcon(pixmap); option->decorationSize = pixmap.size() / pixmap.devicePixelRatio(); break; @@ -340,13 +357,14 @@ void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option, } } - value = index.data(Qt::DisplayRole); - if (value.isValid() && !value.isNull()) { + value = modelRoleDataSpan.dataForRole(Qt::DisplayRole); + if (value->isValid() && !value->isNull()) { option->features |= QStyleOptionViewItem::HasDisplay; - option->text = displayText(value, option->locale); + option->text = displayText(*value, option->locale); } - option->backgroundBrush = qvariant_cast<QBrush>(index.data(Qt::BackgroundRole)); + value = modelRoleDataSpan.dataForRole(Qt::BackgroundRole); + option->backgroundBrush = qvariant_cast<QBrush>(*value); // disable style animations for checkboxes etc. within itemviews (QTBUG-30146) option->styleObject = nullptr; diff --git a/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp b/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp index bf4f33fbd1..df3104eddf 100644 --- a/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp +++ b/tests/auto/corelib/itemmodels/qabstractitemmodel/tst_qabstractitemmodel.cpp @@ -35,6 +35,13 @@ #include "dynamictreemodel.h" +// for testing QModelRoleDataSpan construction +#include <QVarLengthArray> +#include <array> +#include <vector> +#include <deque> +#include <list> + /*! Note that this doesn't test models, but any functionality that QAbstractItemModel should provide */ @@ -108,6 +115,10 @@ private slots: void checkIndex(); + void modelRoleDataSpanConstruction(); + void modelRoleDataSpan(); + + void multiData(); private: DynamicTreeModel *m_model; }; @@ -2384,5 +2395,135 @@ void tst_QAbstractItemModel::checkIndex() QVERIFY(!model.checkIndex(topLevelIndex, QAbstractItemModel::CheckIndexOption::IndexIsValid)); } +template <typename T> +inline constexpr bool CanConvertToSpan = std::is_convertible_v<T, QModelRoleDataSpan>; + +void tst_QAbstractItemModel::modelRoleDataSpanConstruction() +{ + // Compile time test + static_assert(CanConvertToSpan<QModelRoleData &>); + static_assert(CanConvertToSpan<QModelRoleData (&)[123]>); + static_assert(CanConvertToSpan<QVector<QModelRoleData> &>); + static_assert(CanConvertToSpan<QVarLengthArray<QModelRoleData> &>); + static_assert(CanConvertToSpan<std::vector<QModelRoleData> &>); + static_assert(CanConvertToSpan<std::array<QModelRoleData, 123> &>); + + static_assert(!CanConvertToSpan<QModelRoleData>); + static_assert(!CanConvertToSpan<QVector<QModelRoleData>>); + static_assert(!CanConvertToSpan<const QVector<QModelRoleData> &>); + static_assert(!CanConvertToSpan<std::vector<QModelRoleData>>); + static_assert(!CanConvertToSpan<std::deque<QModelRoleData>>); + static_assert(!CanConvertToSpan<std::deque<QModelRoleData> &>); + static_assert(!CanConvertToSpan<std::list<QModelRoleData> &>); + static_assert(!CanConvertToSpan<std::list<QModelRoleData>>); +} + +void tst_QAbstractItemModel::modelRoleDataSpan() +{ + QModelRoleData data[3] = { + QModelRoleData(Qt::DisplayRole), + QModelRoleData(Qt::DecorationRole), + QModelRoleData(Qt::EditRole) + }; + QModelRoleData *dataPtr = data; + + QModelRoleDataSpan span(data); + + QCOMPARE(span.size(), 3); + QCOMPARE(span.length(), 3); + QCOMPARE(span.data(), dataPtr); + QCOMPARE(span.begin(), dataPtr); + QCOMPARE(span.end(), dataPtr + 3); + for (int i = 0; i < 3; ++i) + QCOMPARE(span[i].role(), data[i].role()); + + data[0].setData(42); + data[1].setData(QStringLiteral("a string")); + data[2].setData(123.5); + + QCOMPARE(span.dataForRole(Qt::DisplayRole)->toInt(), 42); + QCOMPARE(span.dataForRole(Qt::DecorationRole)->toString(), "a string"); + QCOMPARE(span.dataForRole(Qt::EditRole)->toDouble(), 123.5); +} + +// model implementing data(), but not multiData(); check that the +// default implementation of multiData() does the right thing +class NonMultiDataRoleModel : public QAbstractListModel +{ + Q_OBJECT + +public: + int rowCount(const QModelIndex &) const override + { + return 1000; + } + + // We handle roles <= 10. All such roles return a QVariant(int) containing + // the same value as the role, except for 10 which returns a string. + QVariant data(const QModelIndex &index, int role) const override + { + Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid)); + + if (role < 10) + return QVariant::fromValue(role); + else if (role == 10) + return QVariant::fromValue(QStringLiteral("Hello!")); + + return QVariant(); + } +}; + +void tst_QAbstractItemModel::multiData() +{ + QModelRoleData data[] = { + QModelRoleData(1), + QModelRoleData(42), + QModelRoleData(5), + QModelRoleData(2), + QModelRoleData(12), + QModelRoleData(2), + QModelRoleData(10), + QModelRoleData(-123) + }; + + QModelRoleDataSpan span(data); + + for (const auto &roledata : span) + QVERIFY(roledata.data().isNull()); + + NonMultiDataRoleModel model; + const QModelIndex index = model.index(0, 0); + QVERIFY(index.isValid()); + + const auto check = [&]() { + for (auto &roledata : span) { + const auto role = roledata.role(); + if (role < 10) { + QVERIFY(!roledata.data().isNull()); + QVERIFY(roledata.data().userType() == qMetaTypeId<int>()); + QCOMPARE(roledata.data().toInt(), role); + } else if (role == 10) { + QVERIFY(!roledata.data().isNull()); + QVERIFY(roledata.data().userType() == qMetaTypeId<QString>()); + QCOMPARE(roledata.data().toString(), QStringLiteral("Hello!")); + } else { + QVERIFY(roledata.data().isNull()); + } + } + }; + + model.multiData(index, span); + check(); + + model.multiData(index, span); + check(); + + for (auto &roledata : span) + roledata.clearData(); + + model.multiData(index, span); + check(); +} + QTEST_MAIN(tst_QAbstractItemModel) #include "tst_qabstractitemmodel.moc" |