summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2025-07-10 17:31:41 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2025-12-04 14:29:20 +0100
commite22cd01076e795ed853b0536605d3bb205587d78 (patch)
treef30f5ce0c6103baf62448a9b76474715545bdf40 /src
parentba5f7153a0e0d4aa9ebaa52f5c4507e5da974bf5 (diff)
Add QRangeModelAdapter: C++-style access to the range while talking QAIM
If a QRangeModel represents a C++ range, then the C++ range must no longer be modified directly, as clients of the model won't be notified about data or structural changes. Ignoring this (documented) warning might end up with views not presenting the data correctly, or even result in crashes as the model cannot update QPersistentModelIndex instances. Modifying the range through the QAbstractItemModel API is ok, but clumsy, as it requires dealing with QModelIndex and QVariant for basic operations. QRangeModelAdapter provides an easy, type safe, and data-structure aware API for reading and also modifying a range that a QRangeModel operates on. This includes an interator API for rows, and - unless the range is a list - columns. Dereferencing row iterators yields a row reference type from which a row can be accessed for reading, or that a new row can be assigned to. Dereferencing a const column iterator yields an item; dereferencing a mutable column iterator yields a reference type that a new item value can be assigned to. Since QRangeModel itself is not a template class (so we don't know the type of the range anymore once it has been created), we have to create the adapter from the range (and optional protocol), which then implicitly creates the model. Constructing the adapter implicitly constructs the model, which is owned by the adapter. QRangeModelAdapter is a value type, using std::shared_ptr for the model so that all copies of the adapter operate on the same model. To be able to set entire multi-role objects as items, introduce a new Qt::ItemDataRole enum value, Qt::RangeModelAdapterRole. This is very similar to Qt::RangeModelDataRole, but QML has specific requirements that QRangeModelAdapter doesn't have, and we want to pass items back and forth without modifying their value category - ie. an item that is a shared_ptr<Object> is not useful for QML (which needs an Object *), but a C++ user expects to get a shared_ptr<Object> from a call to at(), and also expects to be able to set such an item. The code has room for de-duplicating some logic in follow-up commits. [ChangeLog][Core] Added QRangeModelAdapter for C++-style access to a range used in a QRangeModel, while implementing QAbstractItemModel protocol. Change-Id: I3f2f94cb51b850100590fbe2c9a7c9dabbec59bd Reviewed-by: Artem Dyomin <artem.dyomin@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/doc/snippets/CMakeLists.txt1
-rw-r--r--src/corelib/doc/snippets/qrangemodeladapter/main.cpp366
-rw-r--r--src/corelib/global/qnamespace.h2
-rw-r--r--src/corelib/global/qnamespace.qdoc1
-rw-r--r--src/corelib/itemmodels/qrangemodel.cpp21
-rw-r--r--src/corelib/itemmodels/qrangemodel.h8
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h75
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter.h1654
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter.qdoc922
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter_impl.h402
11 files changed, 3436 insertions, 17 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 5d3d3024e0b..dec68c5f9f4 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1231,6 +1231,7 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel
itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h
itemmodels/qitemselectionmodel.cpp itemmodels/qitemselectionmodel.h itemmodels/qitemselectionmodel_p.h
itemmodels/qrangemodel.h itemmodels/qrangemodel_impl.h itemmodels/qrangemodel.cpp
+ itemmodels/qrangemodeladapter.h itemmodels/qrangemodeladapter_impl.h
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel
diff --git a/src/corelib/doc/snippets/CMakeLists.txt b/src/corelib/doc/snippets/CMakeLists.txt
index 55db84ccbea..c0d15463e9c 100644
--- a/src/corelib/doc/snippets/CMakeLists.txt
+++ b/src/corelib/doc/snippets/CMakeLists.txt
@@ -13,6 +13,7 @@ add_library(corelib_snippets OBJECT
qmessageauthenticationcode/main.cpp
qmetatype/registerConverters.cpp
qrangemodel/main.cpp
+ qrangemodeladapter/main.cpp
qstack/main.cpp
qstringlist/main.cpp
qstringlistmodel/main.cpp
diff --git a/src/corelib/doc/snippets/qrangemodeladapter/main.cpp b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp
new file mode 100644
index 00000000000..a0791dd1dd1
--- /dev/null
+++ b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp
@@ -0,0 +1,366 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtCore/qrangemodeladapter.h>
+
+#ifndef QT_NO_WIDGETS
+
+using namespace Qt::StringLiterals;
+
+#include <QtWidgets/qlistview.h>
+#include <QtWidgets/qtableview.h>
+#include <QtWidgets/qtreeview.h>
+#include <vector>
+
+class Book
+{
+ Q_GADGET
+ Q_PROPERTY(QString title READ title)
+ Q_PROPERTY(QString author READ author)
+ Q_PROPERTY(QString summary MEMBER m_summary)
+ Q_PROPERTY(int rating READ rating WRITE setRating)
+public:
+ enum Roles
+ {
+ TitleRole = Qt::UserRole,
+ AuthorRole,
+ SummaryRole,
+ RatingRole,
+ };
+
+ Book() = default;
+ Book(const QString &title, const QString &author);
+
+ // C++ rule of 0: destructor, as well as copy/move operations
+ // provided by the compiler.
+
+ // read-only properties
+ QString title() const { return m_title; }
+ QString author() const { return m_author; }
+
+ // read/writable property with input validation
+ int rating() const { return m_rating; }
+ void setRating(int rating)
+ {
+ m_rating = qBound(0, rating, 5);
+ }
+
+private:
+ QString m_title;
+ QString m_author;
+ QString m_summary;
+ int m_rating = 0;
+};
+
+template <> struct QRangeModel::RowOptions<Book>
+{
+ static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem;
+};
+
+struct TreeRow;
+using Tree = QList<TreeRow *>;
+
+struct TreeRow
+{
+ QString firstColumn;
+ int secondColumn = 0;
+
+ TreeRow() = default;
+ explicit TreeRow(const QString &first, int second, std::optional<Tree> children = std::nullopt)
+ : firstColumn(first), secondColumn(second), m_children(children)
+ {}
+
+ TreeRow *parentRow() const { return m_parent; }
+ void setParentRow(TreeRow *parent) { m_parent = parent; }
+ const std::optional<Tree> &childRows() const { return m_children; }
+ std::optional<Tree> &childRows() { return m_children; }
+
+private:
+ TreeRow *m_parent;
+ std::optional<Tree> m_children;
+
+ template <size_t I> friend inline decltype(auto) get(const TreeRow &row) {
+ static_assert(I < 2);
+ return false;
+ }
+};
+
+namespace std {
+ template<> struct tuple_size<TreeRow> : integral_constant<int, 2> {};
+ template<> struct tuple_element<0, TreeRow> { using type = QString; };
+ template<> struct tuple_element<1, TreeRow> { using type = int; };
+}
+
+void construct_and_use()
+{
+ //! [construct]
+ std::vector<int> data = {1, 2, 3, 4, 5};
+ QRangeModelAdapter adapter(&data);
+ //! [construct]
+
+ //! [use-model]
+ QListView listView;
+ listView.setModel(adapter.model());
+ //! [use-model]
+}
+
+void get_and_set()
+{
+ QListView tableView;
+ //! [get-range]
+ QList<Book> books = {
+ // ...
+ };
+ QRangeModelAdapter adapter(books);
+ tableView.setModel(adapter.model());
+
+ // show UI and where the user can modify the list
+
+ QList<Book> modifiedBooks = adapter;
+ // or
+ modifiedBooks = adapter.range();
+ //! [get-range]
+
+ //! [set-range]
+ // reset to the original
+ adapter = books;
+ // or
+ adapter.setRange(books);
+ //! [set-range]
+}
+
+void dataAccess()
+{
+ int row = 0;
+ int column = 0;
+ int path = 0;
+ int to = 0;
+ int branch = 0;
+
+ QRangeModelAdapter listAdapter(QList<int>{});
+ //! [list-data]
+ QVariant listItem = listAdapter.data(row);
+ //! [list-data]
+
+ QRangeModelAdapter tableAdapter(QList<QList<int>>{});
+ //! [table-data]
+ QVariant tableItem = tableAdapter.data(row, column);
+ //! [table-data]
+
+ QRangeModelAdapter treeAdapter(QList<TreeRow *>{});
+ //! [tree-data]
+ QVariant treeItem = treeAdapter.data({path, to, branch}, column);
+ //! [tree-data]
+
+ //! [multirole-data]
+ QRangeModelAdapter listOfBooks(QList<Book>{
+ // ~~~
+ });
+ QString bookTitle = listOfBooks.data(0, Book::TitleRole).toString();
+ Book multiRoleItem = listOfBooks.data(0).value<Book>();
+ //! [multirole-data]
+}
+
+void list_access()
+{
+ QListView listView;
+ {
+ //! [list-access]
+ QRangeModelAdapter list(std::vector<int>{1, 2, 3, 4, 5});
+ listView.setModel(list.model());
+
+ int firstValue = list.at(0); // == 1
+ list.at(0) = -1;
+ list.at(1) = list.at(4);
+ //! [list-access]
+ }
+ {
+ //! [list-access-multirole]
+ QRangeModelAdapter books(QList<Book>{
+ // ~~~
+ });
+ Book firstBook = books.at(0);
+ Book newBook = {};
+ books.at(0) = newBook; // dataChanged() emitted
+ //! [list-access-multirole]
+
+ //! [list-access-multirole-member-access]
+ QString title = books.at(0)->title();
+ //! [list-access-multirole-member-access]
+
+ //! [list-access-multirole-write-back]
+ // books.at(0)->setRating(5); - not possible even though 'books' is not const
+ firstBook = books.at(0);
+ firstBook.setRating(5);
+ books.at(0) = firstBook; // dataChanged() emitted
+ //! [list-access-multirole-write-back]
+ }
+}
+
+void table_access()
+{
+ QTableView tableView;
+ {
+ //! [table-item-access]
+ QRangeModelAdapter table(std::vector<std::vector<double>>{
+ {1.0, 2.0, 3.0, 4.0, 5.0},
+ {6.0, 7.0, 8.0, 9.0, 10.0},
+ });
+ tableView.setModel(table.model());
+
+ double value = table.at(0, 2); // value == 3.0
+ table.at(0, 2) = value * 2; // table[0, 2] == 6.0
+ //! [table-item-access]
+
+ //! [table-row-const-access]
+ const auto &constTable = table;
+ const std::vector<double> &topRow = constTable.at(0);
+ //! [table-row-const-access]
+
+ //! [table-row-access]
+ auto lastRow = table.at(table.rowCount() - 1);
+ lastRow = { 6.5, 7.5, 8.0, 9.0, 10 }; // emits dataChanged() for entire row
+ //! [table-row-access]
+ }
+
+ {
+ //! [table-mixed-type-access]
+ QRangeModelAdapter table(std::vector<std::tuple<int, QString>>{
+ // ~~~
+ });
+ int number = table.at(0, 0)->toInt();
+ QString text = table.at(0, 1)->toString();
+ //! [table-mixed-type-access]
+ }
+}
+
+void tree_access()
+{
+ QTreeView treeView;
+
+ //! [tree-row-access]
+ QRangeModelAdapter tree = QRangeModelAdapter(Tree{
+ new TreeRow{"Germany", 357002, Tree{
+ new TreeRow("Bavaria", 70550)
+ },
+ },
+ new TreeRow{"France", 632702},
+ });
+ treeView.setModel(tree.model());
+
+ auto germanyData = tree.at(0);
+ auto bavariaData = tree.at({0, 0});
+ //! [tree-row-access]
+
+ //! [tree-item-access]
+ auto germanyName = tree.at(0, 0);
+ auto bavariaSize = tree.at({0, 0}, 1);
+ //! [tree-item-access]
+
+ //! [tree-row-write]
+ // deletes the old row - tree was moved in
+ tree.at({0, 0}) = new TreeRow{"Berlin", 892};
+ //! [tree-row-write]
+}
+
+void read_only()
+{
+#if 0
+ //! [read-only]
+ const QStringList strings = {"On", "Off"};
+ QRangeModelAdapter adapter(strings);
+ adapter.at(0) = "Undecided"; // compile error: return value of 'at' is const
+ adapter.insertRow(0); // compiler error: requirements not satisfied
+ //! [read-only]
+#endif
+}
+
+void list_iterate()
+{
+ QRangeModelAdapter books(QList<Book>{
+ // ~~~
+ });
+ QListView view;
+ view.setModel(books.model());
+
+ //! [ranged-for-const-list]
+ for (const auto &book : std::as_const(books)) {
+ qDebug() << "The book" << book.title()
+ << "written by" << book.author()
+ << "has" << book.rating() << "stars";
+ }
+ //! [ranged-for-const-list]
+
+ //! [ranged-for-mutable-list]
+ for (auto book : books) {
+ qDebug() << "The book" << book->title()
+ << "written by" << book->author()
+ << "has" << book->rating() << "stars";
+
+ Book copy = book;
+ copy.setRating(copy.rating() + 1);
+ book = copy;
+ }
+ //! [ranged-for-mutable-list]
+}
+
+void table_iterate()
+{
+ //! [ranged-for-const-table]
+ QRangeModelAdapter table(std::vector<std::pair<int, QString>>{
+ // ~~~
+ });
+
+ for (const auto &row : std::as_const(table)) {
+ qDebug() << "Number is" << row->first << "and string is" << row->second;
+ }
+ //! [ranged-for-const-table]
+
+ //! [ranged-for-const-table-items]
+ for (const auto &row : std::as_const(table)) {
+ for (const auto &item : row) {
+ qDebug() << item; // item is a QVariant
+ }
+ }
+ //! [ranged-for-const-table-items]
+
+ //! [ranged-for-mutable-table]
+ for (auto row : table) {
+ qDebug() << "Number is" << row->first << "and string is" << row->second;
+ row = std::make_pair(42, u"forty-two"_s);
+ }
+ //! [ranged-for-mutable-table]
+
+ //! [ranged-for-mutable-table-items]
+ for (auto row : table) {
+ for (auto item : row) {
+ item = 42;
+ }
+ }
+ //! [ranged-for-mutable-table-items]
+}
+
+void tree_iterate()
+{
+ QRangeModelAdapter tree = QRangeModelAdapter(Tree{
+ new TreeRow{"1", 1, Tree{
+ new TreeRow("1.1", 11)
+ },
+ },
+ new TreeRow{"2", 2},
+ });
+
+ static_assert(std::is_same_v<typename decltype(tree)::DataReference::value_type, QVariant>);
+
+ //! [ranged-for-tree]
+ for (auto row : tree) {
+ if (row.hasChildren()) {
+ for (auto child : row.children()) {
+ // ~~~
+ }
+ }
+ }
+ //! [ranged-for-tree]
+}
+
+#endif
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h
index fb37e8df8e4..8332d1fd9bf 100644
--- a/src/corelib/global/qnamespace.h
+++ b/src/corelib/global/qnamespace.h
@@ -1533,6 +1533,8 @@ namespace Qt {
WhatsThisPropertyRole = 31,
// QRangeModel support for QML's required property var modelData
RangeModelDataRole = 40,
+ // QRangeModelAdapter support for accessing entire multi-role objects
+ RangeModelAdapterRole = 41,
// Reserved
UserRole = 0x0100,
diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc
index e530b28e7f1..69418af4b68 100644
--- a/src/corelib/global/qnamespace.qdoc
+++ b/src/corelib/global/qnamespace.qdoc
@@ -2830,6 +2830,7 @@
\omitvalue StatusTipPropertyRole
\omitvalue WhatsThisPropertyRole
\omitvalue RangeModelDataRole
+ \omitvalue RangeModelAdapterRole
\omitvalue StandardItemFlagsRole
\omitvalue FileInfoRole
\omitvalue RemoteObjectsCacheRole
diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp
index f533605c721..b4ffea4d87d 100644
--- a/src/corelib/itemmodels/qrangemodel.cpp
+++ b/src/corelib/itemmodels/qrangemodel.cpp
@@ -216,6 +216,27 @@ bool QRangeModelImplBase::connectPropertiesConst(const QModelIndex &index, QObje
return connectPropertiesHelper<QRangeModelImplBase::connectPropertyConst>(index, item, context, properties);
}
+namespace QRangeModelDetails
+{
+Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index)
+{
+ QModelRoleData result[] = {
+ QModelRoleData{Qt::RangeModelAdapterRole},
+ QModelRoleData{Qt::RangeModelDataRole},
+ QModelRoleData{Qt::DisplayRole},
+ };
+ index.multiData(result);
+ QVariant variant;
+ size_t r = 0;
+ do {
+ variant = result[r].data();
+ ++r;
+ } while (!variant.isValid() && r < std::size(result));
+
+ return variant;
+}
+}
+
/*!
\class QRangeModel
\inmodule QtCore
diff --git a/src/corelib/itemmodels/qrangemodel.h b/src/corelib/itemmodels/qrangemodel.h
index d15f40d37a9..b8500f9ae94 100644
--- a/src/corelib/itemmodels/qrangemodel.h
+++ b/src/corelib/itemmodels/qrangemodel.h
@@ -150,6 +150,14 @@ void QRangeModelImplBase::dataChanged(const QModelIndex &from, const QModelIndex
{
m_rangeModel->dataChanged(from, to, roles);
}
+void QRangeModelImplBase::beginResetModel()
+{
+ m_rangeModel->beginResetModel();
+}
+void QRangeModelImplBase::endResetModel()
+{
+ m_rangeModel->endResetModel();
+}
void QRangeModelImplBase::beginInsertColumns(const QModelIndex &parent, int start, int count)
{
m_rangeModel->beginInsertColumns(parent, start, count);
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index a18ba7d0984..7864992633d 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -741,6 +741,8 @@ namespace QRangeModelDetails
} // namespace QRangeModelDetails
class QRangeModel;
+// forward declare so that we can declare friends
+template <typename, typename, typename> class QRangeModelAdapter;
class QRangeModelImplBase : public QtPrivate::QQuasiVirtualInterface<QRangeModelImplBase>
{
@@ -903,6 +905,8 @@ protected:
inline void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to);
inline void dataChanged(const QModelIndex &from, const QModelIndex &to,
const QList<int> &roles);
+ inline void beginResetModel();
+ inline void endResetModel();
inline void beginInsertColumns(const QModelIndex &parent, int start, int count);
inline void endInsertColumns();
inline void beginRemoveColumns(const QModelIndex &parent, int start, int count);
@@ -1215,7 +1219,7 @@ public:
tried = true;
const auto roles = this->itemModel().roleNames().keys();
for (auto &role : roles) {
- if (role == Qt::RangeModelDataRole)
+ if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)
continue;
QVariant data = ItemAccess::readRole(value, role);
if (data.isValid())
@@ -1242,7 +1246,7 @@ public:
return roleNames.key(key.toUtf8(), -1);
}();
- if (role != -1 && role != Qt::RangeModelDataRole)
+ if (role != -1 && role != Qt::RangeModelDataRole && role != Qt::RangeModelAdapterRole)
result.insert(role, QRangeModelDetails::value(it));
}
}
@@ -1253,7 +1257,7 @@ public:
const auto end = roleNames.keyEnd();
for (auto it = roleNames.keyBegin(); it != end; ++it) {
const int role = *it;
- if (role == Qt::RangeModelDataRole)
+ if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)
continue;
QVariant data = readRole(index, role, QRangeModelDetails::pointerTo(value));
if (data.isValid())
@@ -1266,8 +1270,10 @@ public:
if (index.isValid()) {
readAt(index, readItemData);
- if (!tried) // no multi-role item found
+ if (!tried) { // no multi-role item found
result = this->itemModel().QAbstractItemModel::itemData(index);
+ result.remove(Qt::RangeModelAdapterRole);
+ }
}
return result;
}
@@ -1294,6 +1300,12 @@ public:
roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
else
roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
+ } else if (roleData.role() == Qt::RangeModelAdapterRole) {
+ // for QRangeModelAdapter however, we want to respect smart pointer wrappers
+ if constexpr (std::is_copy_assignable_v<value_type>)
+ roleData.setData(QVariant::fromValue(value));
+ else
+ roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
} else {
roleData.setData(ItemAccess::readRole(value, roleData.role()));
}
@@ -1333,6 +1345,12 @@ public:
roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
else
roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
+ } else if (roleData.role() == Qt::RangeModelAdapterRole) {
+ // for QRangeModelAdapter however, we want to respect smart pointer wrappers
+ if constexpr (std::is_copy_assignable_v<value_type>)
+ roleData.setData(QVariant::fromValue(value));
+ else
+ roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
} else {
roleData.setData(readRole(index, roleData.role(),
QRangeModelDetails::pointerTo(value)));
@@ -1355,7 +1373,7 @@ public:
for (auto &roleData : roleDataSpan) {
const int role = roleData.role();
if (role == Qt::DisplayRole || role == Qt::EditRole
- || role == Qt::RangeModelDataRole) {
+ || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) {
roleData.setData(read(value));
} else {
roleData.clearData();
@@ -1377,7 +1395,7 @@ public:
auto emitDataChanged = qScopeGuard([&success, this, &index, role]{
if (success) {
Q_EMIT this->dataChanged(index, index,
- role == Qt::EditRole || role == Qt::RangeModelDataRole
+ role == Qt::EditRole || role == Qt::RangeModelDataRole || role == Qt::RangeModelDataRole
? QList<int>{} : QList<int>{role});
}
});
@@ -1393,13 +1411,22 @@ public:
auto &targetRef = QRangeModelDetails::refTo(target);
constexpr auto targetMetaType = QMetaType::fromType<value_type>();
const auto dataMetaType = data.metaType();
+ constexpr bool isWrapped = QRangeModelDetails::is_wrapped<value_type>();
if constexpr (!std::is_copy_assignable_v<wrapped_value_type>) {
- // This covers move-only types, but also polymorph types like QObject.
- // We don't support replacing a stored object with another one, as this
- // makes object ownership very messy.
- // fall through to error handling
- } else if constexpr (QRangeModelDetails::is_wrapped<value_type>()) {
- if (QRangeModelDetails::isValid(targetRef)) {
+ // we don't support replacing objects that are stored as raw pointers,
+ // as this makes object ownership very messy. But we can replace objects
+ // stored in smart pointers.
+ if constexpr (isWrapped && !std::is_pointer_v<value_type>
+ && std::is_copy_assignable_v<value_type>) {
+ if (data.canConvert(targetMetaType)) {
+ target = data.value<value_type>();
+ return true;
+ }
+ }
+ // Otherwise we have a move-only or polymorph type. fall through to
+ // error handling.
+ } else if constexpr (isWrapped) {
+ if (QRangeModelDetails::isValid(target)) {
// we need to get a wrapped value type out of the QVariant, which
// might carry a pointer. We have to try all alternatives.
if (const auto mt = QMetaType::fromType<wrapped_value_type>();
@@ -1428,16 +1455,17 @@ public:
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
- if (role == Qt::RangeModelDataRole)
+ if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)
return setRangeModelDataRole();
return ItemAccess::writeRole(target, data, role);
} if constexpr (has_metaobject<value_type>) {
if (row_traits::fixed_size() <= 1) { // multi-role value
- if (role == Qt::RangeModelDataRole)
+ if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)
return setRangeModelDataRole();
return writeRole(role, QRangeModelDetails::pointerTo(target), data);
} else if (column <= row_traits::fixed_size() // multi-column
- && (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::RangeModelDataRole)) {
+ && (role == Qt::DisplayRole || role == Qt::EditRole
+ || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)) {
return writeProperty(column, QRangeModelDetails::pointerTo(target), data);
}
} else if constexpr (multi_role::value) {
@@ -1465,7 +1493,7 @@ public:
else
return write(target[roleNames.value(roleToSet)], data);
} else if (role == Qt::DisplayRole || role == Qt::EditRole
- || role == Qt::RangeModelDataRole) {
+ || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) {
return write(target, data);
}
return false;
@@ -1579,7 +1607,7 @@ public:
tried = true;
auto targetCopy = makeCopy(target);
for (auto &&[role, value] : data.asKeyValueRange()) {
- if (role == Qt::RangeModelDataRole)
+ if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)
continue;
if (!writeRole(role, QRangeModelDetails::pointerTo(targetCopy), value)) {
const QByteArray roleName = roleNames.value(role);
@@ -2376,6 +2404,7 @@ protected:
return that().childRangeImpl(index);
}
+ template <typename, typename, typename> friend class QRangeModelAdapter;
ModelData m_data;
};
@@ -2409,6 +2438,18 @@ public:
: Base(std::forward<Range>(model), std::forward<Protocol>(p), itemModel)
{};
+ void setParentRow(range_type &children, row_ptr parent)
+ {
+ for (auto &&child : children)
+ this->protocol().setParentRow(QRangeModelDetails::refTo(child), parent);
+ resetParentInChildren(&children);
+ }
+
+ void deleteRemovedRows(range_type &range)
+ {
+ deleteRemovedRows(QRangeModelDetails::begin(range), QRangeModelDetails::end(range));
+ }
+
protected:
QModelIndex indexImpl(int row, int column, const QModelIndex &parent) const
{
diff --git a/src/corelib/itemmodels/qrangemodeladapter.h b/src/corelib/itemmodels/qrangemodeladapter.h
new file mode 100644
index 00000000000..df74a26663e
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter.h
@@ -0,0 +1,1654 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#ifndef QRANGEMODELADAPTER_H
+#define QRANGEMODELADAPTER_H
+
+#include <QtCore/qrangemodeladapter_impl.h>
+
+QT_BEGIN_NAMESPACE
+
+template <typename Range, typename Protocol = void, typename Model = QRangeModel>
+class QT_TECH_PREVIEW_API QRangeModelAdapter
+{
+ using Impl = QRangeModelDetails::RangeImplementation<Range, Protocol>;
+ using Storage = QRangeModelDetails::AdapterStorage<Model, Impl>;
+
+ Storage storage;
+
+#ifdef Q_QDOC
+ using range_type = Range;
+ using const_row_reference = typename std::iterator_traits<Range>::const_reference;
+ using row_reference = typename std::iterator_traits<Range>::reference;
+#else
+ using range_type = QRangeModelDetails::wrapped_t<Range>;
+ using const_row_reference = typename Impl::const_row_reference;
+ using row_reference = typename Impl::row_reference;
+#endif
+ using range_features = typename QRangeModelDetails::range_traits<range_type>;
+ using row_type = std::remove_reference_t<row_reference>;
+ using row_features = QRangeModelDetails::range_traits<typename Impl::wrapped_row_type>;
+ using row_ptr = typename Impl::wrapped_row_type *;
+ using row_traits = typename Impl::row_traits;
+ using item_type = std::remove_reference_t<typename row_traits::item_type>;
+ using data_type = typename QRangeModelDetails::data_type<item_type>::type;
+ using const_data_type = QRangeModelDetails::asConst_t<data_type>;
+ using protocol_traits = typename Impl::protocol_traits;
+
+ template <typename I> static constexpr bool is_list = I::protocol_traits::is_list;
+ template <typename I> static constexpr bool is_table = I::protocol_traits::is_table;
+ template <typename I> static constexpr bool is_tree = I::protocol_traits::is_tree;
+ template <typename I> static constexpr bool canInsertColumns = I::dynamicColumns()
+ && I::isMutable()
+ && row_features::has_insert;
+ template <typename I> static constexpr bool canRemoveColumns = I::dynamicColumns()
+ && I::isMutable()
+ && row_features::has_erase;
+
+ template <typename I> using if_writable = std::enable_if_t<I::isMutable(), bool>;
+ template <typename I> using if_list = std::enable_if_t<is_list<I>, bool>;
+ template <typename I> using unless_list = std::enable_if_t<!is_list<I>, bool>;
+ template <typename I> using if_table = std::enable_if_t<is_table<I>, bool>;
+ template <typename I> using if_tree = std::enable_if_t<is_tree<I>, bool>;
+ template <typename I> using unless_tree = std::enable_if_t<!is_tree<I>, bool>;
+ template <typename I> using if_flat = std::enable_if_t<is_list<I> || is_table<I>, bool>;
+
+ template <typename I>
+ using if_canInsertRows = std::enable_if_t<I::canInsertRows(), bool>;
+ template <typename I>
+ using if_canRemoveRows = std::enable_if_t<I::canRemoveRows(), bool>;
+ template <typename F>
+ using if_canMoveItems = std::enable_if_t<F::has_rotate || F::has_splice, bool>;
+
+ template <typename I>
+ using if_canInsertColumns = std::enable_if_t<canInsertColumns<I>, bool>;
+ template <typename I>
+ using if_canRemoveColumns = std::enable_if_t<canRemoveColumns<I>, bool>;
+
+ template <typename Row>
+ static constexpr bool is_compatible_row = std::is_convertible_v<Row, const_row_reference>;
+ template <typename Row>
+ using if_compatible_row = std::enable_if_t<is_compatible_row<Row>, bool>;
+
+ template <typename C>
+ static constexpr bool is_compatible_row_range = is_compatible_row<
+ decltype(*std::begin(std::declval<C&>()))
+ >;
+ template <typename C>
+ using if_compatible_row_range = std::enable_if_t<is_compatible_row_range<C>, bool>;
+ template <typename Data>
+ static constexpr bool is_compatible_data = true;
+ // std::is_convertible_v<Data, decltype(*std::begin(std::declval<const_row_reference>()))>;
+ template <typename Data>
+ using if_compatible_data = std::enable_if_t<is_compatible_data<Data>, bool>;
+ template <typename C>
+ static constexpr bool is_compatible_data_range = is_compatible_data<
+ decltype(*std::begin(std::declval<C&>()))
+ >;
+ template <typename C>
+ using if_compatible_data_range = std::enable_if_t<is_compatible_data_range<C>, bool>;
+
+ template <typename R>
+ using if_assignable_range = std::enable_if_t<std::is_assignable_v<range_type, R>, bool>;
+
+ friend class QRangeModel;
+ template <typename T>
+ static constexpr bool is_adapter = QRangeModelDetails::is_any_of<q20::remove_cvref_t<T>,
+ QRangeModelAdapter>::value;
+ template <typename T>
+ using unless_adapter = std::enable_if_t<!is_adapter<T>, bool>;
+
+#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY)
+ // An adapter on a mutable range can make itself an adapter on a const
+ // version of that same range. To make the constructor for a sub-range
+ // accessible, befriend the mutable version. We can use more
+ // generic pattern matching here, as we only use as input what asConst
+ // might produce as output.
+ template <typename T> static constexpr T asMutable(const T &);
+ template <typename T> static constexpr T *asMutable(const T *);
+ template <template <typename, typename...> typename U, typename T, typename ...Args>
+ static constexpr U<T, Args...> asMutable(const U<const T, Args...> &);
+
+ template <typename T>
+ using asMutable_t = decltype(asMutable(std::declval<T>()));
+ friend class QRangeModelAdapter<asMutable_t<Range>, Protocol, Model>;
+#else
+ template <typename R, typename P, typename M>
+ friend class QRangeModelAdapter;
+#endif
+
+ explicit QRangeModelAdapter(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root,
+ std::in_place_t) // disambiguate from range/protocol c'tor
+ : storage{model, root}
+ {}
+
+ explicit QRangeModelAdapter(QRangeModel *model)
+ : storage(model)
+ {}
+
+public:
+ struct DataReference
+ {
+ using value_type = data_type;
+ using const_value_type = const_data_type;
+ using pointer = QRangeModelDetails::data_pointer_t<const_value_type>;
+
+ explicit DataReference(const QModelIndex &index) noexcept
+ : m_index(index)
+ {
+ Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid");
+ }
+
+ DataReference(const DataReference &other) = default;
+
+ // reference (not std::reference_wrapper) semantics
+ DataReference &operator=(const DataReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ ~DataReference() = default;
+
+ DataReference &operator=(const value_type &value)
+ {
+ constexpr Qt::ItemDataRole dataRole = Qt::RangeModelAdapterRole;
+
+ if (m_index.isValid()) {
+ auto model = const_cast<QAbstractItemModel *>(m_index.model());
+ [[maybe_unused]] bool couldWrite = false;
+ if constexpr (std::is_same_v<q20::remove_cvref_t<value_type>, QVariant>)
+ couldWrite = model->setData(m_index, value, dataRole);
+ else
+ couldWrite = model->setData(m_index, QVariant::fromValue(value), dataRole);
+#ifndef QT_NO_DEBUG
+ if (!couldWrite) {
+ qWarning() << "Writing value of type" << QMetaType::fromType<value_type>().name()
+ << "to role" << dataRole << "at index" << m_index
+ << "of the model failed";
+ }
+ } else {
+ qCritical("Data reference for invalid index, can't write to model");
+#endif
+ }
+ return *this;
+ }
+
+ const_value_type get() const
+ {
+ Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid");
+ return QRangeModelDetails::dataAtIndex<q20::remove_cvref_t<value_type>>(m_index);
+ }
+
+ operator const_value_type() const
+ {
+ return get();
+ }
+
+ pointer operator->() const
+ {
+ return {get()};
+ }
+
+ bool isValid() const { return m_index.isValid(); }
+
+ private:
+ QModelIndex m_index;
+
+ friend inline bool comparesEqual(const DataReference &lhs, const DataReference &rhs)
+ {
+ return lhs.m_index == rhs.m_index
+ || lhs.get() == rhs.get();
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference);
+
+ friend inline bool comparesEqual(const DataReference &lhs, const value_type &rhs)
+ {
+ return lhs.get() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference, value_type);
+
+ friend inline void swap(DataReference lhs, DataReference rhs)
+ {
+ const value_type lhsValue = lhs.get();
+ lhs = rhs;
+ rhs = lhsValue; // no point in moving, we have to go through QVariant anyway
+ }
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const DataReference &ref)
+ {
+ return dbg << ref.get();
+ }
+#endif
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator<<(QDataStream &ds, const DataReference &ref)
+ {
+ return ds << ref.get();
+ }
+ friend inline QDataStream &operator>>(QDataStream &ds, DataReference &ref)
+ {
+ value_type value;
+ ds >> value;
+ ref = value;
+ return ds;
+ }
+#endif
+ };
+ template <typename Iterator, typename Adapter>
+ struct ColumnIteratorBase
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using difference_type = int;
+
+ ColumnIteratorBase() = default;
+ ColumnIteratorBase(const ColumnIteratorBase &other) = default;
+ ColumnIteratorBase(ColumnIteratorBase &&other) = default;
+ ColumnIteratorBase &operator=(const ColumnIteratorBase &other) = default;
+ ColumnIteratorBase &operator=(ColumnIteratorBase &&other) = default;
+
+ ColumnIteratorBase(const QModelIndex &rowIndex, int column, Adapter *adapter) noexcept
+ : m_rowIndex(rowIndex), m_column(column), m_adapter(adapter)
+ {
+ }
+
+ void swap(ColumnIteratorBase &other) noexcept
+ {
+ using std::swap;
+ swap(m_rowIndex, other.m_rowIndex);
+ swap(m_column, other.m_column);
+ q_ptr_swap(m_adapter, other.m_adapter);
+ }
+
+ friend Iterator &operator++(Iterator &that) noexcept
+ {
+ ++that.m_column;
+ return that;
+ }
+ friend Iterator operator++(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ ++that;
+ return copy;
+ }
+ friend Iterator operator+(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_rowIndex, that.m_column + n, that.m_adapter};
+ }
+ friend Iterator operator+(difference_type n, const Iterator &that) noexcept
+ {
+ return that + n;
+ }
+ friend Iterator &operator+=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_column += n;
+ return that;
+ }
+
+ friend Iterator &operator--(Iterator &that) noexcept
+ {
+ --that.m_column;
+ return that;
+ }
+ friend Iterator operator--(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ --that;
+ return copy;
+ }
+ friend Iterator operator-(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_rowIndex, that.m_column - n, that.m_adapter};
+ }
+ friend Iterator operator-(difference_type n, const Iterator &that) noexcept
+ {
+ return that - n;
+ }
+ friend Iterator &operator-=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_column -= n;
+ return that;
+ }
+
+ friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ Q_PRE(lhs.m_rowIndex == rhs.m_rowIndex);
+ Q_PRE(lhs.m_adapter == rhs.m_adapter);
+ return lhs.m_column - rhs.m_column;
+ }
+
+ protected:
+ ~ColumnIteratorBase() = default;
+ QModelIndex m_rowIndex;
+ int m_column = -1;
+ Adapter *m_adapter = nullptr;
+
+ private:
+ friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs)
+ {
+ Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex);
+ return lhs.m_column == rhs.m_column;
+ }
+ friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs)
+ {
+ Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex);
+ return qCompareThreeWay(lhs.m_column, rhs.m_column);
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Iterator)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Iterator &it)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "ColumnIterator(" << it.m_rowIndex.siblingAtColumn(it.m_column) << ")";
+ }
+#endif
+ };
+
+ struct ConstColumnIterator : ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter>
+ {
+ using Base = ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter>;
+ using difference_type = typename Base::difference_type;
+ using value_type = data_type;
+ using reference = const_data_type;
+ using pointer = QRangeModelDetails::data_pointer_t<value_type>;
+
+ using Base::Base;
+ using Base::operator=;
+ ~ConstColumnIterator() = default;
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ return std::as_const(this->m_adapter)->at(this->m_rowIndex.row(), this->m_column);
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ struct ColumnIterator : ColumnIteratorBase<ColumnIterator, QRangeModelAdapter>
+ {
+ using Base = ColumnIteratorBase<ColumnIterator, QRangeModelAdapter>;
+ using difference_type = typename Base::difference_type;
+ using value_type = DataReference;
+ using reference = DataReference;
+ using pointer = reference;
+
+ using Base::Base;
+ using Base::operator=;
+ ~ColumnIterator() = default;
+
+ operator ConstColumnIterator() const
+ {
+ return ConstColumnIterator{this->m_rowIndex, this->m_column, this->m_adapter};
+ }
+
+ pointer operator->() const
+ {
+ return operator*();
+ }
+
+ reference operator*() const
+ {
+ return reference{this->m_rowIndex.siblingAtColumn(this->m_column)};
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ template <typename Reference, typename const_row_type, typename = void>
+ struct RowGetter
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ auto *childRange = impl->childRange(that->m_index.parent());
+ if constexpr (std::is_convertible_v<const row_type &, const_row_type>) {
+ return *std::next(QRangeModelDetails::begin(childRange), that->m_index.row());
+ } else {
+ const auto &row = *std::next(QRangeModelDetails::begin(childRange),
+ that->m_index.row());
+ return const_row_type{QRangeModelDetails::begin(row), QRangeModelDetails::end(row)};
+ }
+ }
+
+ const_row_type operator->() const
+ {
+ return {get()};
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename const_row_type>
+ struct RowGetter<Reference, const_row_type,
+ std::enable_if_t<std::is_reference_v<const_row_type>>>
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ using QRangeModelDetails::begin;
+
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))),
+ that->m_index.row());
+ }
+
+ auto operator->() const
+ {
+ return std::addressof(get());
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename const_row_type>
+ struct RowGetter<Reference, const_row_type,
+ std::enable_if_t<std::is_pointer_v<const_row_type>>>
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ using QRangeModelDetails::begin;
+
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))),
+ that->m_index.row());
+ }
+
+ const_row_type operator->() const
+ {
+ return get();
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename Adapter>
+ struct RowReferenceBase : RowGetter<Reference, QRangeModelDetails::asConstRow_t<row_type>>
+ {
+ using const_iterator = ConstColumnIterator;
+ using size_type = int;
+ using difference_type = int;
+ using const_row_type = QRangeModelDetails::asConstRow_t<row_type>;
+
+ RowReferenceBase(const QModelIndex &index, Adapter *adapter) noexcept
+ : m_index(index), m_adapter(adapter)
+ {}
+
+ template <typename I = Impl, if_tree<I> = true>
+ bool hasChildren() const
+ {
+ return m_adapter->model()->hasChildren(m_index);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ auto children() const
+ {
+ using ConstRange = QRangeModelDetails::asConst_t<Range>;
+ return QRangeModelAdapter<ConstRange, Protocol, Model>(m_adapter->storage.m_model,
+ m_index, std::in_place);
+ }
+
+ ConstColumnIterator cbegin() const
+ {
+ return ConstColumnIterator{m_index, 0, m_adapter};
+ }
+ ConstColumnIterator cend() const
+ {
+ return ConstColumnIterator{m_index, m_adapter->columnCount(), m_adapter};
+ }
+
+ ConstColumnIterator begin() const { return cbegin(); }
+ ConstColumnIterator end() const { return cend(); }
+
+ size_type size() const
+ {
+ return m_adapter->columnCount();
+ }
+
+ auto at(int column) const
+ {
+ Q_ASSERT(column >= 0 && column < m_adapter->columnCount());
+ return *ConstColumnIterator{m_index, column, m_adapter};
+ }
+
+ auto operator[](int column) const
+ {
+ return at(column);
+ }
+
+ protected:
+ friend struct RowGetter<Reference, const_row_type>;
+ ~RowReferenceBase() = default;
+ QModelIndex m_index;
+ Adapter *m_adapter;
+
+ private:
+ friend bool comparesEqual(const Reference &lhs, const Reference &rhs)
+ {
+ Q_ASSERT(lhs.m_adapter == rhs.m_adapter);
+ return lhs.m_index == rhs.m_index;
+ }
+ friend Qt::strong_ordering compareThreeWay(const Reference &lhs, const Reference &rhs)
+ {
+ Q_ASSERT(lhs.m_adapter == rhs.m_adapter);
+ return qCompareThreeWay(lhs.m_index, rhs.m_index);
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Reference)
+
+ friend bool comparesEqual(const Reference &lhs, const row_type &rhs)
+ {
+ return lhs.get() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(Reference, row_type)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Reference &ref)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "RowReference(" << ref.m_index << ")";
+ }
+#endif
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator<<(QDataStream &ds, const Reference &ref)
+ {
+ return ds << ref.get();
+ }
+#endif
+ };
+
+ struct ConstRowReference : RowReferenceBase<ConstRowReference, const QRangeModelAdapter>
+ {
+ using Base = RowReferenceBase<ConstRowReference, const QRangeModelAdapter>;
+ using Base::Base;
+
+ ConstRowReference() = default;
+ ConstRowReference(const ConstRowReference &) = default;
+ ConstRowReference(ConstRowReference &&) = default;
+ ConstRowReference &operator=(const ConstRowReference &) = default;
+ ConstRowReference &operator=(ConstRowReference &&) = default;
+ ~ConstRowReference() = default;
+ };
+
+ struct RowReference : RowReferenceBase<RowReference, QRangeModelAdapter>
+ {
+ using Base = RowReferenceBase<RowReference, QRangeModelAdapter>;
+ using iterator = ColumnIterator;
+ using const_iterator = typename Base::const_iterator;
+ using size_type = typename Base::size_type;
+ using difference_type = typename Base::difference_type;
+ using const_row_type = typename Base::const_row_type;
+
+ using Base::Base;
+ RowReference() = delete;
+ ~RowReference() = default;
+ RowReference(const RowReference &other) = default;
+ RowReference(RowReference &&other) = default;
+
+ // assignment has reference (std::reference_wrapper) semantics
+ RowReference &operator=(const ConstRowReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ RowReference &operator=(const RowReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ RowReference &operator=(const row_type &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ RowReference &operator=(row_type &&other)
+ {
+ assign(std::move(other));
+ return *this;
+ }
+
+ operator ConstRowReference() const
+ {
+ return ConstRowReference{this->m_index, this->m_adapter};
+ }
+
+ template <typename ConstRowType = const_row_type,
+ std::enable_if_t<!std::is_same_v<ConstRowType, const row_type &>, bool> = true>
+ RowReference &operator=(const ConstRowType &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ template <typename T, typename It, typename Sentinel>
+ RowReference &operator=(const QRangeModelDetails::RowView<T, It, Sentinel> &other)
+ {
+ *this = row_type{other.begin(), other.end()};
+ return *this;
+ }
+
+ friend inline void swap(RowReference lhs, RowReference rhs)
+ {
+ auto lhsRow = lhs.get();
+ lhs = rhs.get();
+ rhs = std::move(lhsRow);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ auto children()
+ {
+ return QRangeModelAdapter(this->m_adapter->storage.m_model, this->m_index,
+ std::in_place);
+ }
+
+ using Base::begin;
+ ColumnIterator begin()
+ {
+ return ColumnIterator{this->m_index, 0, this->m_adapter};
+ }
+
+ using Base::end;
+ ColumnIterator end()
+ {
+ return ColumnIterator{this->m_index, this->m_adapter->columnCount(), this->m_adapter};
+ }
+
+ using Base::at;
+ auto at(int column)
+ {
+ Q_ASSERT(column >= 0 && column < this->m_adapter->columnCount());
+ return *ColumnIterator{this->m_index, column, this->m_adapter};
+ }
+
+ using Base::operator[];
+ auto operator[](int column)
+ {
+ return at(column);
+ }
+
+ private:
+ template <typename RHS>
+ void verifyRows(const row_type &oldRow, const RHS &newRow)
+ {
+ using namespace QRangeModelDetails;
+ if constexpr (test_size<row_type>::value) {
+ // prevent that tables get populated with wrongly sized rows
+ Q_ASSERT_X(Impl::size(newRow) == Impl::size(oldRow),
+ "RowReference::operator=()",
+ "The new row has the wrong size!");
+ }
+
+ if constexpr (is_tree<Impl>) {
+ // we cannot hook invalid rows up to the tree hierarchy
+ Q_ASSERT_X(isValid(newRow),
+ "RowReference::operator=()",
+ "An invalid row can not inserted into a tree!");
+ }
+ }
+
+ template <typename R>
+ void assign(R &&other)
+ {
+ auto *impl = this->m_adapter->storage.implementation();
+ decltype(auto) oldRow = impl->rowData(this->m_index);
+
+ verifyRows(oldRow, other);
+
+ if constexpr (is_tree<Impl>) {
+ using namespace QRangeModelDetails;
+ auto &protocol = impl->protocol();
+ auto *oldParent = protocol.parentRow(refTo(oldRow));
+
+ // the old children will be removed; we don't try to overwrite
+ // them with the new children, we replace them completely
+ if (decltype(auto) oldChildren = protocol.childRows(refTo(oldRow));
+ isValid(oldChildren)) {
+ if (int oldChildCount = this->m_adapter->model()->rowCount(this->m_index)) {
+ impl->beginRemoveRows(this->m_index, 0, oldChildCount - 1);
+ impl->deleteRemovedRows(refTo(oldChildren));
+ // make sure the list is empty before we emit rowsRemoved
+ refTo(oldChildren) = range_type{};
+ impl->endRemoveRows();
+ }
+ }
+
+ if constexpr (protocol_traits::has_deleteRow)
+ protocol.deleteRow(oldRow);
+ oldRow = std::forward<R>(other);
+ if constexpr (protocol_traits::has_setParentRow) {
+ protocol.setParentRow(refTo(oldRow), oldParent);
+ if (decltype(auto) newChildren = protocol.childRows(refTo(oldRow));
+ isValid(newChildren)) {
+ impl->beginInsertRows(this->m_index, 0, Impl::size(refTo(newChildren)) - 1);
+ impl->setParentRow(refTo(newChildren), pointerTo(oldRow));
+ impl->endInsertRows();
+ }
+ }
+ } else {
+ oldRow = other;
+ }
+ this->m_adapter->emitDataChanged(this->m_index,
+ this->m_index.siblingAtColumn(this->m_adapter->columnCount() - 1));
+ }
+
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator>>(QDataStream &ds, RowReference &ref)
+ {
+ row_type value;
+ ds >> value;
+ ref = value;
+ return ds;
+ }
+#endif
+ };
+
+ template <typename Iterator, typename Adapter>
+ struct RowIteratorBase : QRangeModelDetails::ParentIndex<is_tree<Impl>>
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using difference_type = int;
+
+ RowIteratorBase() = default;
+ RowIteratorBase(const RowIteratorBase &) = default;
+ RowIteratorBase(RowIteratorBase &&) = default;
+ RowIteratorBase &operator=(const RowIteratorBase &) = default;
+ RowIteratorBase &operator=(RowIteratorBase &&) = default;
+
+ RowIteratorBase(int row, const QModelIndex &parent, Adapter *adapter)
+ : QRangeModelDetails::ParentIndex<is_tree<Impl>>{parent}
+ , m_row(row), m_adapter(adapter)
+ {}
+
+ void swap(RowIteratorBase &other) noexcept
+ {
+ using std::swap;
+ swap(m_row, other.m_row);
+ swap(this->m_rootIndex, other.m_rootIndex);
+ q_ptr_swap(m_adapter, other.m_adapter);
+ }
+
+ friend Iterator &operator++(Iterator &that) noexcept
+ {
+ ++that.m_row;
+ return that;
+ }
+ friend Iterator operator++(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ ++that;
+ return copy;
+ }
+ friend Iterator operator+(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_row + n, that.root(), that.m_adapter};
+ }
+ friend Iterator operator+(difference_type n, const Iterator &that) noexcept
+ {
+ return that + n;
+ }
+ friend Iterator &operator+=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_row += n;
+ return that;
+ }
+
+ friend Iterator &operator--(Iterator &that) noexcept
+ {
+ --that.m_row;
+ return that;
+ }
+ friend Iterator operator--(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ --that;
+ return copy;
+ }
+ friend Iterator operator-(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_row - n, that.root(), that.m_adapter};
+ }
+ friend Iterator operator-(difference_type n, const Iterator &that) noexcept
+ {
+ return that - n;
+ }
+ friend Iterator &operator-=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_row -= n;
+ return that;
+ }
+
+ friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ return lhs.m_row - rhs.m_row;
+ }
+
+ protected:
+ ~RowIteratorBase() = default;
+ int m_row = -1;
+ Adapter *m_adapter = nullptr;
+
+ private:
+ friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ return lhs.m_row == rhs.m_row && lhs.root() == rhs.root();
+ }
+ friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ if (lhs.root() == rhs.root())
+ return qCompareThreeWay(lhs.m_row, rhs.m_row);
+ return qCompareThreeWay(lhs.root(), rhs.root());
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED(Iterator)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Iterator &it)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "RowIterator(" << it.m_row << it.root() << ")";
+ }
+#endif
+ };
+
+public:
+ struct ConstRowIterator : public RowIteratorBase<ConstRowIterator, const QRangeModelAdapter>
+ {
+ using Base = RowIteratorBase<ConstRowIterator, const QRangeModelAdapter>;
+ using Base::Base;
+
+ using difference_type = typename Base::difference_type;
+ using value_type = std::conditional_t<is_list<Impl>,
+ const_data_type,
+ ConstRowReference>;
+ using reference = std::conditional_t<is_list<Impl>,
+ const_data_type,
+ ConstRowReference>;
+ using pointer = std::conditional_t<is_list<Impl>,
+ QRangeModelDetails::data_pointer_t<const_data_type>,
+ ConstRowReference>;
+
+ ConstRowIterator(const ConstRowIterator &other) = default;
+ ConstRowIterator(ConstRowIterator &&other) = default;
+ ConstRowIterator &operator=(const ConstRowIterator &other) = default;
+ ConstRowIterator &operator=(ConstRowIterator &&other) = default;
+ ~ConstRowIterator() = default;
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ if constexpr (is_list<Impl>) {
+ return this->m_adapter->at(this->m_row);
+ } else {
+ const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0,
+ this->root());
+ return ConstRowReference{index, this->m_adapter};
+ }
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ struct RowIterator : public RowIteratorBase<RowIterator, QRangeModelAdapter>
+ {
+ using Base = RowIteratorBase<RowIterator, QRangeModelAdapter>;
+ using Base::Base;
+
+ using difference_type = typename Base::difference_type;
+ using value_type = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+ using reference = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+ using pointer = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+
+ RowIterator(const RowIterator &other) = default;
+ RowIterator(RowIterator &&other) = default;
+ RowIterator &operator=(const RowIterator &other) = default;
+ RowIterator &operator=(RowIterator &&other) = default;
+ ~RowIterator() = default;
+
+ operator ConstRowIterator() const
+ {
+ return ConstRowIterator{this->m_row, this->root(), this->m_adapter};
+ }
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0, this->root());
+ if constexpr (is_list<Impl>) {
+ return reference{index};
+ } else {
+ return reference{index, this->m_adapter};
+ }
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ using const_iterator = ConstRowIterator;
+ using iterator = RowIterator;
+
+ template <typename R, typename P,
+ std::enable_if_t<!std::is_void_v<P>, bool> = true>
+ explicit QRangeModelAdapter(R &&range, P &&protocol)
+ : QRangeModelAdapter(new Model(std::forward<R>(range), std::forward<P>(protocol)))
+ {}
+
+ template <typename R, typename P = void,
+ unless_adapter<R> = true,
+ std::enable_if_t<std::is_void_v<P>, bool> = true>
+ explicit QRangeModelAdapter(R &&range)
+ : QRangeModelAdapter(new Model(std::forward<R>(range)))
+ {}
+
+ // compiler-generated copy/move SMF are fine!
+
+ Model *model() const
+ {
+ return storage.m_model.get();
+ }
+
+ const range_type &range() const
+ {
+ return QRangeModelDetails::refTo(storage.implementation()->childRange(storage.root()));
+ }
+
+ Q_IMPLICIT operator const range_type &() const
+ {
+ return range();
+ }
+
+ template <typename NewRange = range_type, if_assignable_range<NewRange> = true>
+ void setRange(NewRange &&newRange)
+ {
+ using namespace QRangeModelDetails;
+
+ auto *impl = storage.implementation();
+ const QModelIndex root = storage.root();
+ const qsizetype newLastRow = qsizetype(Impl::size(refTo(newRange))) - 1;
+ auto *oldRange = impl->childRange(root);
+ const qsizetype oldLastRow = qsizetype(Impl::size(oldRange)) - 1;
+
+ if (!root.isValid()) {
+ impl->beginResetModel();
+ impl->deleteOwnedRows();
+ } else if constexpr (is_tree<Impl>) {
+ if (oldLastRow > 0) {
+ impl->beginRemoveRows(root, 0, model()->rowCount(root) - 1);
+ impl->deleteRemovedRows(refTo(oldRange));
+ impl->endRemoveRows();
+ }
+ if (newLastRow > 0)
+ impl->beginInsertRows(root, 0, newLastRow);
+ } else {
+ Q_ASSERT_X(false, "QRangeModelAdapter::setRange",
+ "Internal error: The root index in a table or list must be invalid.");
+ }
+ refTo(oldRange) = std::forward<NewRange>(newRange);
+ if (!root.isValid()) {
+ impl->endResetModel();
+ } else if constexpr (is_tree<Impl>) {
+ if (newLastRow > 0) {
+ Q_ASSERT(model()->hasChildren(root));
+ // if it was moved, then newRange is now likely to be empty. Get
+ // the inserted row.
+ impl->setParentRow(refTo(impl->childRange(storage.root())),
+ pointerTo(impl->rowData(root)));
+ impl->endInsertRows();
+ }
+ }
+ }
+
+ template <typename NewRange = range_type, if_assignable_range<NewRange> = true>
+ QRangeModelAdapter &operator=(NewRange &&newRange)
+ {
+ setRange(std::forward<NewRange>(newRange));
+ return *this;
+ }
+
+ // iterator API
+ ConstRowIterator cbegin() const
+ {
+ return ConstRowIterator{ 0, storage.root(), this };
+ }
+ ConstRowIterator begin() const { return cbegin(); }
+
+ ConstRowIterator cend() const
+ {
+ return ConstRowIterator{ rowCount(), storage.root(), this };
+ }
+ ConstRowIterator end() const { return cend(); }
+
+ template <typename I = Impl, if_writable<I> = true>
+ RowIterator begin()
+ {
+ return RowIterator{ 0, storage.root(), this };
+ }
+
+ template <typename I = Impl, if_writable<I> = true>
+ RowIterator end()
+ {
+ return RowIterator{ rowCount(), storage.root(), this };
+ }
+
+ int size() const
+ {
+ return rowCount();
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QModelIndex index(int row) const
+ {
+ return storage->index(row, 0, storage.root());
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QModelIndex index(int row, int column) const
+ {
+ return storage->index(row, column, storage.root());
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QModelIndex index(QSpan<const int> path, int col) const
+ {
+ Q_PRE(path.size());
+ QModelIndex result = storage.root();
+ auto count = path.size();
+ for (const int r : path) {
+ if (--count)
+ result = storage->index(r, 0, result);
+ else
+ result = storage->index(r, col, result);
+ }
+ return result;
+ }
+
+ int columnCount() const
+ {
+ // all rows and tree branches have the same column count
+ return storage->columnCount({});
+ }
+
+ int rowCount() const
+ {
+ return storage->rowCount(storage.root());
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ int rowCount(int row) const
+ {
+ return storage->rowCount(index(row, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ int rowCount(QSpan<const int> path) const
+ {
+ return storage->rowCount(index(path, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ constexpr bool hasChildren(int row) const
+ {
+ return storage.m_model->hasChildren(index(row, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ constexpr bool hasChildren(QSpan<const int> path) const
+ {
+ return storage.m_model->hasChildren(index(path, 0));
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QVariant data(int row) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row));
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QVariant data(int row, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row), role);
+ }
+
+ template <typename I = Impl, if_list<I> = true, if_writable<I> = true>
+ bool setData(int row, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(row), value, role);
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QVariant data(int row, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column));
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QVariant data(int row, int column, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column), role);
+ }
+
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ bool setData(int row, int column, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(row, column), value, role);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QVariant data(QSpan<const int> path, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QVariant data(QSpan<const int> path, int column, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column), role);
+ }
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ bool setData(QSpan<const int> path, int column, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(path, column), value, role);
+ }
+
+ // at/operator[int] for list: returns value at row
+ // if multi-role value: return the entire object
+ template <typename I= Impl, if_list<I> = true>
+ const_data_type at(int row) const
+ {
+ return QRangeModelDetails::dataAtIndex<data_type>(index(row));
+ }
+ template <typename I = Impl, if_list<I> = true>
+ const_data_type operator[](int row) const { return at(row); }
+
+ template <typename I= Impl, if_list<I> = true, if_writable<I> = true>
+ auto at(int row) { return DataReference{this->index(row)}; }
+ template <typename I = Impl, if_list<I> = true, if_writable<I> = true>
+ auto operator[](int row) { return DataReference{this->index(row)}; }
+
+ // at/operator[int] for table or tree: a reference or view of the row
+ template <typename I = Impl, unless_list<I> = true>
+ decltype(auto) at(int row) const
+ {
+ return ConstRowReference{index(row, 0), this}.get();
+ }
+ template <typename I = Impl, unless_list<I> = true>
+ decltype(auto) operator[](int row) const { return at(row); }
+
+ template <typename I = Impl, if_table<I> = true, if_writable<I> = true>
+ decltype(auto) at(int row)
+ {
+ return RowReference{index(row, 0), this};
+ }
+ template <typename I = Impl, if_table<I> = true, if_writable<I> = true>
+ decltype(auto) operator[](int row) { return at(row); }
+
+ // at/operator[int, int] for table: returns value at row/column
+ template <typename I = Impl, unless_list<I> = true>
+ const_data_type at(int row, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<data_type>(index(row, column));
+ }
+
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, unless_list<I> = true>
+ const_data_type operator[](int row, int column) const { return at(row, column); }
+#endif
+
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ auto at(int row, int column)
+ {
+ return DataReference{this->index(row, column)};
+ }
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ auto operator[](int row, int column) { return at(row, column); }
+#endif
+
+ // at/operator[int] for tree: return a wrapper that maintains reference to
+ // parent.
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(int row)
+ {
+ return RowReference{index(row, 0), this};
+ }
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](int row) { return at(row); }
+
+ // at/operator[path] for tree: a reference or view of the row
+ template <typename I = Impl, if_tree<I> = true>
+ decltype(auto) at(QSpan<const int> path) const
+ {
+ return ConstRowReference{index(path, 0), this}.get();
+ }
+ template <typename I = Impl, if_tree<I> = true>
+ decltype(auto) operator[](QSpan<const int> path) const { return at(path); }
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(QSpan<const int> path)
+ {
+ return RowReference{index(path, 0), this};
+ }
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](QSpan<const int> path) { return at(path); }
+
+ // at/operator[path, column] for tree: return value
+ template <typename I = Impl, if_tree<I> = true>
+ const_data_type at(QSpan<const int> path, int column) const
+ {
+ Q_PRE(path.size());
+ return QRangeModelDetails::dataAtIndex<data_type>(index(path, column));
+ }
+
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, if_tree<I> = true>
+ const_data_type operator[](QSpan<const int> path, int column) const { return at(path, column); }
+#endif
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(QSpan<const int> path, int column)
+ {
+ Q_PRE(path.size());
+ return DataReference{this->index(path, column)};
+ }
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](QSpan<const int> path, int column) { return at(path, column); }
+#endif
+
+ template <typename I = Impl, if_canInsertRows<I> = true>
+ bool insertRow(int before)
+ {
+ return storage.m_model->insertRow(before);
+ }
+
+ template <typename I = Impl, if_canInsertRows<I> = true, if_tree<I> = true>
+ bool insertRow(QSpan<const int> before)
+ {
+ Q_PRE(before.size());
+ return storage.m_model->insertRow(before.back(), this->index(before.first(before.size() - 1), 0));
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row<D> = true>
+ bool insertRow(int before, D &&data)
+ {
+ return insertRowImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row<D> = true, if_tree<I> = true>
+ bool insertRow(QSpan<const int> before, D &&data)
+ {
+ return insertRowImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row_range<C> = true>
+ bool insertRows(int before, C &&data)
+ {
+ return insertRowsImpl(before, storage.root(), std::forward<C>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row_range<C> = true, if_tree<I> = true>
+ bool insertRows(QSpan<const int> before, C &&data)
+ {
+ return insertRowsImpl(before.back(), this->index(before.first(before.size() - 1), 0),
+ std::forward<C>(data));
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true>
+ bool removeRow(int row)
+ {
+ return removeRows(row, 1);
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true>
+ bool removeRow(QSpan<const int> path)
+ {
+ return removeRows(path, 1);
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true>
+ bool removeRows(int row, int count)
+ {
+ return storage->removeRows(row, count, storage.root());
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true>
+ bool removeRows(QSpan<const int> path, int count)
+ {
+ return storage->removeRows(path.back(), count,
+ this->index(path.first(path.size() - 1), 0));
+ }
+
+ template <typename F = range_features, if_canMoveItems<F> = true>
+ bool moveRow(int source, int destination)
+ {
+ return moveRows(source, 1, destination);
+ }
+
+ template <typename F = range_features, if_canMoveItems<F> = true>
+ bool moveRows(int source, int count, int destination)
+ {
+ return storage->moveRows(storage.root(), source, count, storage.root(), destination);
+ }
+
+ template <typename I = Impl, typename F = range_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveRow(QSpan<const int> source, QSpan<const int> destination)
+ {
+ return moveRows(source, 1, destination);
+ }
+
+ template <typename I = Impl, typename F = range_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveRows(QSpan<const int> source, int count, QSpan<const int> destination)
+ {
+ return storage->moveRows(this->index(source.first(source.size() - 1), 0),
+ source.back(),
+ count,
+ this->index(destination.first(destination.size() - 1), 0),
+ destination.back());
+ }
+
+ template <typename I = Impl, if_canInsertColumns<I> = true>
+ bool insertColumn(int before)
+ {
+ return storage.m_model->insertColumn(before);
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertColumns<I> = true, if_compatible_data<D> = true>
+ bool insertColumn(int before, D &&data)
+ {
+ return insertColumnImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertColumns<I> = true, if_compatible_data_range<C> = true>
+ bool insertColumns(int before, C &&data)
+ {
+ return insertColumnsImpl(before, storage.root(), std::forward<C>(data));
+ }
+
+ template <typename I = Impl, if_canRemoveColumns<I> = true>
+ bool removeColumn(int column)
+ {
+ return storage.m_model->removeColumn(column);
+ }
+
+ template <typename I = Impl, if_canRemoveColumns<I> = true>
+ bool removeColumns(int column, int count)
+ {
+ return storage->removeColumns(column, count, {});
+ }
+
+ template <typename F = row_features, if_canMoveItems<F> = true>
+ bool moveColumn(int from, int to)
+ {
+ return moveColumns(from, 1, to);
+ }
+
+ template <typename F = row_features, if_canMoveItems<F> = true>
+ bool moveColumns(int from, int count, int to)
+ {
+ return storage->moveColumns(storage.root(), from, count, storage.root(), to);
+ }
+
+ template <typename I = Impl, typename F = row_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveColumn(QSpan<const int> source, int to)
+ {
+ const QModelIndex parent = this->index(source.first(source.size() - 1), 0);
+ return storage->moveColumns(parent, source.back(), 1, parent, to);
+ }
+
+ template <typename I = Impl, typename F = row_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveColumns(QSpan<const int> source, int count, int destination)
+ {
+ const QModelIndex parent = this->index(source.first(source.size() - 1), 0);
+ return storage->moveColumns(parent, source.back(), count, parent, destination);
+ }
+
+private:
+ friend inline
+ bool comparesEqual(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs) noexcept
+ {
+ return lhs.storage.m_model == rhs.storage.m_model;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(QRangeModelAdapter)
+
+ friend inline
+ bool comparesEqual(const QRangeModelAdapter &lhs, const range_type &rhs)
+ {
+ return lhs.range() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(QRangeModelAdapter, range_type)
+
+
+ void emitDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+ {
+ Q_EMIT storage.implementation()->dataChanged(topLeft, bottomRight, {});
+ }
+
+ template <typename P>
+ static auto setParentRow(P protocol, row_type &newRow, row_ptr parentRow)
+ -> decltype(protocol.setParentRow(std::declval<row_type&>(), std::declval<row_ptr>()))
+ {
+ return protocol.setParentRow(newRow, parentRow);
+ }
+
+ template <typename ...Args> static constexpr void setParentRow(Args &&...) {}
+
+ template <typename D>
+ bool insertRowImpl(int before, const QModelIndex &parent, D &&data)
+ {
+ return storage.implementation()->doInsertRows(before, 1, parent, [&data, this]
+ (range_type &range, auto parentRow, int row, int count) {
+ Q_UNUSED(this);
+ const auto oldSize = range.size();
+ auto newRow = range.emplace(QRangeModelDetails::pos(range, row), std::forward<D>(data));
+ setParentRow(storage.implementation()->protocol(), *newRow, parentRow);
+ return range.size() == oldSize + count;
+ });
+ }
+
+ template <typename LHS>
+ static auto selfInsertion(LHS *lhs, LHS *rhs) -> decltype(lhs == rhs)
+ {
+ if (lhs == rhs) {
+#ifndef QT_NO_DEBUG
+ qCritical("Inserting data into itself is not supported");
+#endif
+ return true;
+ }
+ return false;
+ }
+ template <typename LHS, typename RHS>
+ static constexpr bool selfInsertion(LHS *, RHS *) { return false; }
+
+ template <typename C>
+ bool insertRowsImpl(int before, const QModelIndex &parent, C &&data)
+ {
+ bool result = false;
+ result = storage->doInsertRows(before, int(std::size(data)), parent, [&data, this]
+ (range_type &range, auto parentRow, int row, int count){
+ Q_UNUSED(parentRow);
+ Q_UNUSED(this);
+ const auto pos = QRangeModelDetails::pos(range, row);
+ const auto oldSize = range.size();
+
+ auto dataRange = [&data]{
+ if constexpr (std::is_rvalue_reference_v<C&&>) {
+ return std::make_pair(
+ std::move_iterator(std::begin(data)),
+ std::move_iterator(std::end(data))
+ );
+ } else {
+ return std::make_pair(std::begin(data), std::end(data));
+ }
+ }();
+
+ if constexpr (range_features::has_insert_range) {
+ if (selfInsertion(&range, &data))
+ return false;
+ auto start = range.insert(pos, dataRange.first, dataRange.second);
+ if constexpr (protocol_traits::has_setParentRow) {
+ while (count) {
+ setParentRow(storage->protocol(), *start, parentRow);
+ ++start;
+ --count;
+ }
+ } else {
+ Q_UNUSED(start);
+ }
+ } else {
+ auto newRow = range.insert(pos, count, row_type{});
+ while (dataRange.first != dataRange.second) {
+ *newRow = *dataRange.first;
+ setParentRow(storage->protocol(), *newRow, parentRow);
+ ++dataRange.first;
+ ++newRow;
+ }
+ }
+ return range.size() == oldSize + count;
+ });
+ return result;
+ }
+
+ template <typename D, typename = void>
+ struct DataFromList {
+ static constexpr auto first(D &data) { return &data; }
+ static constexpr auto next(D &, D *entry) { return entry; }
+ };
+
+ template <typename D>
+ struct DataFromList<D, std::enable_if_t<QRangeModelDetails::range_traits<D>::value>>
+ {
+ static constexpr auto first(D &data) { return std::begin(data); }
+ static constexpr auto next(D &data, typename D::iterator entry)
+ {
+ ++entry;
+ if (entry == std::end(data))
+ entry = first(data);
+ return entry;
+ }
+ };
+
+ template <typename D, typename = void> struct RowFromTable
+ {
+ static constexpr auto first(D &data) { return &data; }
+ static constexpr auto next(D &, D *entry) { return entry; }
+ };
+
+ template <typename D>
+ struct RowFromTable<D, std::enable_if_t<std::conjunction_v<
+ QRangeModelDetails::range_traits<D>,
+ QRangeModelDetails::range_traits<typename D::value_type>
+ >>
+ > : DataFromList<D>
+ {};
+
+ template <typename D>
+ bool insertColumnImpl(int before, const QModelIndex &parent, D data)
+ {
+ auto entry = DataFromList<D>::first(data);
+
+ return storage->doInsertColumns(before, 1, parent, [&entry, &data]
+ (auto &range, auto pos, int count) {
+ const auto oldSize = range.size();
+ range.insert(pos, *entry);
+ entry = DataFromList<D>::next(data, entry);
+ return range.size() == oldSize + count;
+ });
+ }
+
+ template <typename C>
+ bool insertColumnsImpl(int before, const QModelIndex &parent, C data)
+ {
+ bool result = false;
+ auto entries = RowFromTable<C>::first(data);
+ auto begin = std::begin(*entries);
+ auto end = std::end(*entries);
+ result = storage->doInsertColumns(before, int(std::size(*entries)), parent,
+ [&begin, &end, &entries, &data](auto &range, auto pos, int count) {
+ const auto oldSize = range.size();
+ if constexpr (row_features::has_insert_range) {
+ range.insert(pos, begin, end);
+ } else {
+ auto start = range.insert(pos, count, {});
+ std::copy(begin, end, start);
+ }
+ entries = RowFromTable<C>::next(data, entries);
+ begin = std::begin(*entries);
+ end = std::end(*entries);
+ return range.size() == oldSize + count;
+ });
+ return result;
+ }
+};
+
+template <typename Range, typename Protocol>
+QRangeModelAdapter(Range &&, Protocol &&) -> QRangeModelAdapter<Range, Protocol>;
+
+template <typename Range>
+QRangeModelAdapter(Range &&) -> QRangeModelAdapter<Range, void>;
+
+QT_END_NAMESPACE
+
+#endif // QRANGEMODELADAPTER_H
diff --git a/src/corelib/itemmodels/qrangemodeladapter.qdoc b/src/corelib/itemmodels/qrangemodeladapter.qdoc
new file mode 100644
index 00000000000..5ab128a8c5f
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter.qdoc
@@ -0,0 +1,922 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+// Qt-Security score:insignificant reason:default
+
+#include <QtCore/qrangemodeladapter.h>
+
+/*!
+ \class QRangeModelAdapter
+ \inmodule QtCore
+ \since 6.11
+ \preliminary
+ \ingroup model-view
+ \brief QRangeModelAdapter provides QAbstractItemModel-compliant access to any C++ range.
+ \compares equality
+ \compareswith equality Range
+ \endcompareswith
+
+ QRangeModelAdapter provides a type-safe and structure-aware C++ API around
+ a C++ range and a QRangeModel. Modifications made to the C++ range using
+ the adapter will inform clients of the QRangeModel about the changes. This
+ makes sure that item views are updated, caches are cleaned, and persistent
+ item indexes are invalidated and adapted correctly.
+
+ \section1 Construction and model ownership
+
+ QRangeModelAdapter has to be constructed from a C++ range. As with
+ QRangeModel, the range can be provided by lvalue or rvalue reference, as a
+ reference wrapper, and as a raw or smart pointer.
+
+ \snippet qrangemodeladapter/main.cpp construct
+
+ Constructing the adapter from a range implicitly constructs a QRangeModel
+ instance from that same range. Use model() to get it, and pass it to Qt
+ Widgets or Qt Quick item views as usual.
+
+ \snippet qrangemodeladapter/main.cpp use-model
+
+ The adapter owns the model. QRangeModelAdapter is a value type, so it can be
+ copied and moved. All copies share the same QRangeModel, which will be
+ destroyed when the last copy of the adapter is destroyed.
+
+ If the adapter was created from an lvalue or rvalue reference, then the
+ adapter and model will operate on a copy of the original range object.
+ Otherwise, modifications made through the adapter or model will be written
+ to the original range object. To get the updated range, use the range()
+ function explicitly, or use the implicit conversion of the adapter to a
+ the range.
+
+ \snippet qrangemodeladapter/main.cpp get-range
+
+ To replace the entire range data with data from another (compatible) range,
+ use the setRange() function or the assignment operator.
+
+ \snippet qrangemodeladapter/main.cpp set-range
+
+ \section1 Accessing item data
+
+ The QRangeModelAdapter API provides type-safe read and write access to the
+ range that the model operates on. The adapter API is based on the typical
+ API for C++ containers and ranges, including iterators. To access
+ individual rows and items, use at(), the corresponding subscript
+ \c{operator[]}, or data(). Which overloads of those functions are available
+ depends on the range for which the adapter was constructed.
+
+ \section2 Reading item data as a QVariant
+
+ The data() function always returns a QVariant with the value stored at the
+ specified position and role. In a list, an item can be accessed by a single
+ integer value specifying the row:
+
+ \snippet qrangemodeladapter/main.cpp list-data
+
+ If the range is a table, then items are specified by row and column:
+
+ \snippet qrangemodeladapter/main.cpp table-data
+
+ If the range is a tree, then items are located using a path of rows, and a
+ single column value:
+
+ \snippet qrangemodeladapter/main.cpp tree-data
+
+ Using a single integer as the row provides access to the toplevel tree
+ items.
+
+ \snippet qrangemodeladapter/main.cpp multirole-data
+
+ If no role is specified, then the QVariant will hold the entire item at the
+ position. Use the \l{QVariant::fromValue()} template function to retrieve a
+ copy of the item.
+
+ \section2 Reading and writing using at()
+
+ That the data() function returns a QVariant makes it flexible, but removes
+ type safety. For ranges where all items are of the same type, the at()
+ function provides a type-safe alternative that is more compatible with
+ regular C++ containers. As with data(), at() overloads exist to access an
+ item at a row for lists, at a row/column pair for table, and a path/column
+ pair for trees. However, at() always returns the whole item at the
+ specified position; it's not possible to read an individual role values for
+ an item.
+
+ As expected from a C++ container API, the const overloads of at() (and the
+ corresponding subscript \c{operator[]}) provide immutable access to the
+ value, while the mutable overloads return a reference object that a new
+ value can be assigned to. Note that a QRangeModelAdapter operating on a
+ const range behaves in that respect like a const QRangeModelAdapter. The
+ mutable overloads are removed from the overload set, so the compiler will
+ always select the const version. Trying to call a function that modifies a
+ range will result in a compiler error, even if the adapter itself is
+ mutable:
+
+ \snippet qrangemodeladapter/main.cpp read-only
+
+ The returned reference objects are wrappers that convert implicitly to the
+ underlying type, have a \c{get()} function to explicitly access the
+ underlying value, and an \c{operator->()} that provides direct access to
+ const member functions. However, to prevent accidental data changes that
+ would bypass the QAbstractItemModel notification protocol, those reference
+ objects prevent direct modifications of the items.
+
+ \note Calling mutable overloads generates some overhead. Make a const copy
+ of the adapter (which will not copy the data), or use \c{std::as_const} to
+ make sure that only the const overloads are used in performance critical,
+ read-heavy code. This is the same technique as when accessing elements in
+ an implicitly shared Qt container.
+
+ \section3 Item access
+
+ If the range is represented as a list, then only the overloads taking a row
+ are available.
+
+ \snippet qrangemodeladapter/main.cpp list-access
+
+ The const overload returns the item at that row, while the mutable overload
+ returns a wrapper that implicitly converts to and from the value type of the
+ list.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole
+
+ Assign a value to the wrapper to modify the data in the list. The model will
+ emit \l{QAbstractItemModel::}{dataChanged()} for all roles.
+
+ When using the mutable overloads, you can also access the item type's const
+ members using the overloaded arrow operator.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole-member-access
+
+ It is not possible to access non-const members of the item. Such
+ modifications would bypass the adapter, which couldn't notify the model
+ about the changes. To modify the value stored in the model, make a copy,
+ modify the properties of the copy, and then write that copy back.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole-write-back
+
+ This will make the model emit \l{QAbstractItemModel::}{dataChanged()} for
+ this item, and for all roles.
+
+ If the range is represented as a table, then you can access an individual
+ item by row and columns, using the \l{at(int, int)}{at(row, column)}
+ overload. For trees, that overload gives access to top-level items, while
+ the \l{at(QSpan<const int>, int)}{at(path, column)} overload provides
+ access to items nested within the tree.
+
+ Accessing an individual item in a table or tree is equivalent to accessing
+ an item in a list.
+
+ \snippet qrangemodeladapter/main.cpp table-item-access
+
+ If the range doesn't store all columns using the same data type, then
+ \c{at(row,column)} returns a (a reference wrapper with a) QVariant holding
+ the item.
+
+ \snippet qrangemodeladapter/main.cpp table-mixed-type-access
+
+ \section2 Accessing rows in tables and trees
+
+ For tables and trees, the overloads of at() and subscript \c{operator[]}
+ without the column parameter provide access to the entire row. The value
+ returned by the const overloads will be a reference type that gives access
+ to the row data. If that row holds pointers, then that reference type will
+ be a view of the row, giving access to pointers to const items.
+
+ \section3 Table row access
+
+ The \l{at(int)} overload is still available, but it returns the entire table
+ wor. This makes it possible to work with all the values in the row at once.
+
+ \snippet qrangemodeladapter/main.cpp table-row-const-access
+
+ As with items, the const overload provides direct access to the row type,
+ while the mutable overload returns a wrapper that acts as a reference to the
+ row. The wrapper provides access to const member functions using the
+ overloaded arrow operator. To modify the values, write a modified row type
+ back.
+
+ \snippet qrangemodeladapter/main.cpp table-row-access
+
+ When assigning a new value to the row, then the model emits the
+ \l{QAbstractItemModel::}{dataChanged()} signal for all items in the row,
+ and for all roles.
+
+ \note When using a row type with runtime sizes, such as a \c{std::vector} or
+ a QList, make sure that the new row has the correct size.
+
+ \section3 Tree row access
+
+ Rows in trees are specified by a sequence of integers, one entry for each
+ level in the tree. Note that in the following snippets, the Tree is a range
+ holding raw row pointers, and that the adapter is created with an rvalue
+ reference of that range. This gives the QRangeModel ownership over the row
+ data.
+
+ \snippet qrangemodeladapter/main.cpp tree-row-access
+
+ The overload of \l{at(int)}{at()} taking a single row value provides
+ access to the top-level rows, or items in the top-level rows.
+
+ \snippet qrangemodeladapter/main.cpp tree-item-access
+
+ The basic pattern for accessing rows and items in a tree is identical to
+ accessing rows and items in a table. However, the adapter will make sure
+ that the tree structure is maintained when modifying entire rows.
+
+ \snippet qrangemodeladapter/main.cpp tree-row-write
+
+ In this example, a new row object is created, and assigned to the first
+ child of the first top-level item.
+
+ \section1 Iterator API
+
+ Use begin() and end() to get iterators over the rows of the model, or use
+ ranged-for. If the range is a list of items, dereferencing the iterator
+ will give access to item data.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-list
+
+ As with the const and mutable overloads of at(), a mutable iterator will
+ dereference to a wrapper.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-list
+
+ Use the overloaded arrow operator to access const members of the item type,
+ and assign to the wrapper to replace the value.
+
+ It the range is a table or tree, then iterating over the model will give
+ access to the rows.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-table
+
+ Both the const and the mutable iterator will dereference to a wrapper type
+ for the row. This make sure that we can consistently iterate over each
+ column, even if the underlying row type is not a range (e.g. it might be a
+ tuple or gadget).
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-table-items
+
+ When iterating over a mutable table we can overwrite the entire row.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table
+
+ The model emits the \l{QAbstractItemModel::}{dataChanged()} signal for
+ all items in all row, and for all roles.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table-items
+
+ Iterating over the mutable rows allows us to modify individual items.
+
+ When iterating over a tree, the row wrapper has two additional member
+ functions, hasChildren() and children(), that allow us to traverse the
+ entire tree using iterators.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-tree
+
+ The object returned by children() is a QRangeModelAdapter operating on
+ the same model as the callee, but all operations will use the source row
+ index as the parent index.
+
+ \sa QRangeModel
+*/
+
+/*!
+ \typedef QRangeModelAdapter::const_row_reference
+*/
+
+/*!
+ \typedef QRangeModelAdapter::row_reference
+*/
+
+/*!
+ \typedef QRangeModelAdapter::range_type
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range, Protocol &&protocol)
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range)
+
+ Constructs a QRangeModelAdapter that operates on \a range. For tree ranges,
+ the optional \a protocol will be used for tree traversal.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator==(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs)
+
+ \return whether \a lhs is equal to \a rhs. Two adapters are equal if they
+ both hold the same \l{model()}{model} instance.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator!=(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs)
+
+ \return whether \a lhs is not equal to \a rhs. Two adapters are equal if
+ they both hold the same \l{model()}{model} instance.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> Model *QRangeModelAdapter<Range, Protocol, Model>::model() const
+
+ \return the QRangeModel instance created by this adapter.
+
+ \sa range(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> const QRangeModelAdapter<Range, Protocol, Model>::range_type &QRangeModelAdapter<Range, Protocol, Model>::range() const
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::operator const QRangeModelAdapter<Range, Protocol, Model>::range_type &() const
+
+ \return a const reference to the range that the model adapter operates on.
+
+ \sa setRange(), at(), model()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(NewRange &&newRange)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(NewRange &&newRange)
+
+ Assigns \a newRange to the stored range, possibly using move semantics.
+ This function makes the model() emit the \l{QAbstractItemModel::}{modelAboutToBeReset()}
+ and \l{QAbstractItemModel::}{modelReset()} signals.
+
+ \constraints \c Range is mutable, and \a newRange is of a type that can be
+ assigned to \c Range.
+
+ \sa range(), at(), model()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row) const
+ \overload
+
+ Returns the QModelIndex for \a row.
+
+ \constraints \c Range is a one-dimensional list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row, int column) const
+ \overload
+
+ Returns the QModelIndex for the item at \a row, \a column.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(QSpan<const int> path, int column) const
+ \overload
+
+ Returns the QModelIndex for the item at \a column for the row in the tree
+ specified by \a path.
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::columnCount() const
+
+ \return the number of columns. This will be one if the \c Range represents a
+ list, otherwise this returns be the number of elements in each row.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::rowCount() const
+ \overload
+
+ \return the number of rows. If the \c Range represents a list or table, then
+ this is the number of rows. For trees, this is the number of top-level rows.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(QSpan<const int> row) const
+ \overload
+
+ \return the number of rows under \a row.
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(QSpan<const int> row) const
+ \overload
+
+ \return whether there are any rows under \a row.
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item at \a row, or an invalid QVariant if there is no item. If \a role is
+ not specified, then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a list.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, const QVariant &value, int role)
+
+ Sets the \a role data for the item at \a row to \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is a mutable list.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item referred to by a \a row and \a column, or an invalid QVariant if there
+ is no data stored for that position or role. If \a role is not specified,
+ then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a table or tree.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, int column, const QVariant &value, int role)
+
+ Sets the \a role data for the item referred to by \a row and \a column to
+ \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is mutable, and not a list.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item referred to by \a path and \a column, or an invalid QVariant if there
+ is no data stored for that position or role. If \a role is not specified,
+ then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a tree.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(QSpan<const int> path, int column, const QVariant &value, int role)
+
+ Sets the \a role data for the item referred to by \a path and \a column to
+ \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is a mutable tree.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const
+
+ \return the value at \a row as the type stored in \c Range.
+
+ \constraints \c Range is a list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return the value at \a row wrapped into a mutable reference to the type
+ stored in \c Range.
+
+//! [data-ref]
+ \note Modifications to the range will invalidate that reference. To modify
+ the reference, assign a new value to it. Unless the value stored in the
+ \c Range is a pointer, it is not possible to access individual members of
+ the stored value.
+//! [data-ref]
+
+ \constraints \c Range is a mutable list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const
+
+ \return a constant reference to the row at \a row, as stored in \c Range.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return a mutable reference to the row at \a row, as stored in \c Range.
+
+ \constraints \c Range is a mutable table, but not a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return a mutable wrapper holding a reference to the tree row specified by \a row.
+
+//! [treerow-ref]
+ To modify the tree row, assign a new value to it. Assigning a new tree row
+ will set the parent the new tree row to be the parent of the old tree row.
+ However, neither the old nor the new tree row must have any child rows. To
+ access the tree row, dereferencing the wrapper using \c{operator*()}, or use
+ \c{operator->()} to access tree row members.
+
+ \note Modifications to the range will invalidate the wrapper.
+//! [treerow-ref]
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column) const
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column) const
+\endomit
+
+ \return a copy of the value stored as the item specified by \a row and
+ \a column. If the item is a multi-role item, then this returns a copy of
+ the entire item. If the rows in the \c Range store different types at
+ different columns, then the return type will be a QVariant.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column)
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column)
+\endomit
+
+ \return a mutable reference to the value stored as the item specified by
+ \a row and \a column. If the item is a multi-role item, then this will be
+ a reference to the entire item.
+
+ \include qrangemodeladapter.qdoc data-ref
+
+ \constraints \c Range is a mutable table.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path)
+
+ \return a mutable wrapper holding a reference to the tree row specified by \a path.
+
+ \include qrangemodeladapter.qdoc treerow-ref
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column) const
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column) const
+\endomit
+
+ \return a copy of the value stored as the item specified by \a path and
+ \a column. If the item is a multi-role item, then this returns a copy of
+ the entire item. If the rows in the \c Range store different types at
+ different columns, then the return type will be a QVariant.
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column)
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column)
+\endomit
+
+ \return a mutable reference to the value stored as the item specified by
+ \a path and \a column. If the item is a multi-role item, then this will be
+ a reference to the entire item. If the rows in the \c Range store different
+ types at different columns, then the return type will be a QVariant.
+
+ \include qrangemodeladapter.qdoc data-ref
+
+ \constraints \c Range is a mutable tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before)
+ \overload
+
+ Inserts a single empty row before the row at \a before, and returns whether
+ the insertion was successful.
+//! [insert-row-appends]
+ If \a before is the same value as rowCount(), then the new row will be appended.
+//! [insert-row-appends]
+
+ \constraints \c Range supports insertion of elements.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before)
+ \overload
+
+ Inserts a single empty row before the row at the path specified by \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before, D &&data)
+ \overload
+
+ Inserts a single row constructed from \a data before the row at \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range supports insertion of elements, and if
+ a row can be constructed from \a data.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before, D &&data)
+ \overload
+
+ Inserts a single row constructed from \a data before the row at \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements, and if
+ a row can be constructed from \a data.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(int before, C &&data)
+ \overload
+
+ Inserts rows constructed from the elements in \a data before the row at
+ \a before, and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range supports insertion of elemnets, and if
+ rows can be constructed from the elements in \a data.
+
+ \sa insertRow(), removeRows(), insertColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(QSpan<const int> before, C &&data)
+ \overload
+
+ Inserts rows constructed from the elements in \a data before the row at
+ \a before, and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements, and if
+ rows can be constructed from the elements in \a data.
+
+ \sa insertRow(), removeRows(), insertColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(int row)
+ \overload
+
+ Removes the given \a row and returns whether the removal was successful.
+
+ \constraints \c Range supports the removal of elements.
+
+ \sa removeRows(), removeColumn(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(QSpan<const int> path)
+ \overload
+
+ Removes the row at the given \a path, including all children of that row,
+ and returns whether the removal was successful.
+
+ \constraints \c Range is a tree that supports the removal of elements.
+
+ \sa removeRows(), removeColumn(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(int row, int count)
+ \overload
+
+ Removes \a count rows starting at \a row, and returns whether the removal
+ was successful.
+
+ \constraints \c Range supports the removal of elements.
+
+ \sa removeRow(), removeColumns(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(QSpan<const int> path, int count)
+ \overload
+
+ Removes \a count rows starting at the row specified by \a path, and returns
+ whether the removal was successful.
+
+ \constraints \c Range is a tree that supports the removal of elements.
+
+ \sa removeRow(), removeColumns(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(int source, int destination)
+ \overload
+
+ Moves the row at \a source to the position at \a destination, and returns
+ whether the row was successfully moved.
+
+ \constraints \c Range supports moving of elements.
+
+ \sa insertRow(), removeRow(), moveColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(QSpan<const int> source, QSpan<const int> destination)
+ \overload
+
+ Moves the tree branch at \a source to the position at \a destination, and
+ returns whether the branch was successfully moved.
+
+ \constraints \c Range is a tree that supports moving of elements.
+
+ \sa insertRow(), removeRow(), moveColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(int source, int count, int destination)
+ \overload
+
+ Moves \a count rows starting at \a source to the position at \a destination,
+ and returns whether the rows were successfully moved.
+
+ \constraints \c Range supports moving of elements.
+
+ \sa insertRows(), removeRows(), moveColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(QSpan<const int> source, int count, QSpan<const int> destination)
+ \overload
+
+ Moves \a count tree branches starting at \a source to the position at
+ \a destination, and returns whether the rows were successfully moved.
+
+ \constraints \c Range is a tree that supports moving of elements.
+
+ \sa insertRows(), removeRows(), moveColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before)
+ \overload
+
+ Inserts a single empty column before the column specified by \a before into
+ all rows, and returns whether the insertion was successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ \constraints \c Range has rows that support insertion of elements.
+
+ \sa removeColumn(), insertColumns(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before, D &&data)
+ \overload
+
+ Inserts a single column constructed from \a data before the column specified
+ by \a before into all rows, and returns whether the insertion was successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ If \a data is a single value, then the new entry in all rows will be constructed
+ from that single value.
+
+ If \a data is a container, then the elements in that container will be used
+ sequentially to construct the column for each subsequent row. If there are
+ fewer elements in \a data than there are rows, then function wraps around
+ and starts again from the first element.
+
+ \code
+ \endcode
+
+ \constraints \c Range has rows that support insertion of elements, and the
+ elements can be constructed from the entries in \a data.
+
+ \sa removeColumn(), insertColumns(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumns(int before, C &&data)
+
+ Inserts columns constructed from the elements in \a data before the column
+ specified by \a before into all rows, and returns whether the insertion was
+ successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ If the elements in \a data are values, then the new entries in all rows will
+ be constructed from those values.
+
+ If the elements in \a data are containers, then the entries in the outer
+ container will be used sequentially to construct the new entries for each
+ subsequent row. If there are fewer elements in \a data than there are rows,
+ then the function wraps around and starts again from the first element.
+
+ \code
+ \endcode
+
+ \constraints \c Range has rows that support insertion of elements, and the
+ elements can be constructed from the entries in \a data.
+
+ \sa removeColumns(), insertColumn(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumn(int column)
+
+ Removes the given \a column from each row, and returns whether the removal
+ was successful.
+
+ \constraints \c Range has rows that support removal of elements.
+
+ \sa insertColumn(), removeColumns(), removeRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumns(int column, int count)
+
+ Removes \a count columns starting by the given \a column from each row, and
+ returns whether the removal was successful.
+
+ \constraints \c Range has rows that support removal of elements.
+
+ \sa insertColumns(), removeColumn(), removeRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(int from, int to)
+
+ Moves the column at \a from to the column at \a to, and returns whether the
+ column was successfully moved.
+
+ \constraints \c Range has rows that support moving of elements.
+
+ \sa insertColumn(), removeColumn(), moveColumns(), moveRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(int from, int count, int to)
+
+ Moves \a count columns starting at \a from to the position at \a to, and
+ returns whether the columns were successfully moved.
+
+ \constraints \c Range has rows that support moving of elements.
+
+ \sa insertColumns(), removeColumns(), moveColumn(), moveRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(QSpan<const int> source, int to)
+ \internal Not possible to create a tree from a row type that can rotate/splice?
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(QSpan<const int> source, int count, int to)
+ \internal Not possible to create a tree from a row type that can rotate/splice?
+*/
diff --git a/src/corelib/itemmodels/qrangemodeladapter_impl.h b/src/corelib/itemmodels/qrangemodeladapter_impl.h
new file mode 100644
index 00000000000..ad1dd152b22
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter_impl.h
@@ -0,0 +1,402 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#ifndef QRANGEMODELADAPTER_IMPL_H
+#define QRANGEMODELADAPTER_IMPL_H
+
+#ifndef Q_QDOC
+
+#ifndef QRANGEMODELADAPTER_H
+#error Do not include qrangemodeladapter_impl.h directly
+#endif
+
+#if 0
+#pragma qt_sync_skip_header_check
+#pragma qt_sync_stop_processing
+#endif
+
+#include <QtCore/qrangemodel.h>
+#include <QtCore/qspan.h>
+#include <set>
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QRangeModelDetails
+{
+template <typename Range, typename Protocol>
+using RangeImplementation = std::conditional_t<std::is_void_v<Protocol>,
+ std::conditional_t<is_tree_range<Range>::value,
+ QGenericTreeItemModelImpl<Range, DefaultTreeProtocol<Range>>,
+ QGenericTableItemModelImpl<Range>
+ >,
+ QGenericTreeItemModelImpl<Range, Protocol>
+ >;
+
+// we can't use wrapped_t, we only want to unpack smart pointers, and maintain
+// the pointer nature of the type.
+template <typename T, typename = void>
+struct data_type { using type = T; };
+template <>
+struct data_type<void> { using type = QVariant; };
+
+// pointer types of iterators use QtPrivate::ArrowProxy if the type does not
+// provide operator->() (or is a pointer).
+template <typename T, typename = void> struct test_pointerAccess : std::false_type {};
+template <typename T> struct test_pointerAccess<T *> : std::true_type {};
+template <typename T>
+struct test_pointerAccess<T, std::void_t<decltype(std::declval<T>().operator->())>>
+ : std::true_type
+{};
+
+template <typename T>
+using data_pointer_t = std::conditional_t<test_pointerAccess<T>::value,
+ T, QtPrivate::ArrowProxy<T>>;
+
+// Helpers to make a type const "in depth", taking into account raw pointers
+// and wrapping types, like smart pointers and std::reference_wrapper.
+
+// We need to return data by value, not by reference, as we might only have
+// temporary values (i.e. a QVariant returned by QAIM::data).
+template <typename T, typename = void> struct AsConstData { using type = T; };
+template <typename T> struct AsConstData<const T &> { using type = T; };
+template <typename T> struct AsConstData<T *> { using type = const T *; };
+template <template <typename> typename U, typename T>
+struct AsConstData<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>>
+{ using type = U<const T>; };
+template <typename T> struct AsConstData<std::reference_wrapper<T>>
+{ using type = std::reference_wrapper<const T>; };
+
+template <typename T> using asConst_t = typename AsConstData<T>::type;
+
+// Rows get wrapped into a "view", as a begin/end iterator/sentinel pair.
+// The iterator dereferences to the const version of the value returned by
+// the underlying iterator.
+// Could be replaced with std::views::sub_range in C++ 20.
+template <typename const_row_type, typename Iterator, typename Sentinel>
+struct RowView
+{
+ // this is similar to C++23's std::basic_const_iterator, but we don't want
+ // to convert to the underlying const_iterator.
+ struct iterator
+ {
+ using value_type = asConst_t<typename Iterator::value_type>;
+ using difference_type = typename Iterator::difference_type;
+ using pointer = QRangeModelDetails::data_pointer_t<value_type>;
+ using reference = value_type;
+ using const_reference = value_type;
+ using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
+
+ template <typename I, typename Category>
+ static constexpr bool is_atLeast = std::is_base_of_v<Category,
+ typename std::iterator_traits<I>::iterator_category>;
+ template <typename I, typename Category>
+ using if_atLeast = std::enable_if_t<is_atLeast<I, Category>, bool>;
+
+ reference operator*() const { return *m_it; }
+ pointer operator->() const { return operator*(); }
+
+ // QRM requires at least forward_iterator, so we provide both post- and
+ // prefix increment unconditionally
+ friend constexpr iterator &operator++(iterator &it)
+ noexcept(noexcept(++std::declval<Iterator&>()))
+ {
+ ++it.m_it;
+ return it;
+ }
+ friend constexpr iterator operator++(iterator &it, int)
+ noexcept(noexcept(std::declval<Iterator&>()++))
+ {
+ iterator copy = it;
+ ++copy.m_it;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true>
+ friend constexpr iterator &operator--(iterator &it)
+ noexcept(noexcept(--std::declval<I&>()))
+ {
+ --it.m_it;
+ return it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true>
+ friend constexpr iterator operator--(iterator &it, int)
+ noexcept(noexcept(std::declval<I&>()--))
+ {
+ iterator copy = it;
+ --it.m_it;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator &operator+=(iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() += 1))
+ {
+ it.m_it += n;
+ return it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator &operator-=(iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() -= 1))
+ {
+ it.m_it -= n;
+ return it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator+(const iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() + 1))
+ {
+ iterator copy = it;
+ copy.m_it += n;
+ return copy;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator+(difference_type n, const iterator &it)
+ noexcept(noexcept(1 + std::declval<I&>()))
+ {
+ return it + n;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator-(const iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() - 1))
+ {
+ iterator copy = it;
+ copy.m_it = it.m_it - n;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ constexpr reference operator[](difference_type n) const
+ noexcept(noexcept(I::operator[]()))
+ {
+ return m_it[n];
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr difference_type operator-(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() - std::declval<I&>()))
+ {
+ return lhs.m_it - rhs.m_it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator<(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() < std::declval<I&>()))
+ {
+ return lhs.m_it < rhs.m_it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator<=(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() <= std::declval<I&>()))
+ {
+ return lhs.m_it <= rhs.m_it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator>(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() > std::declval<I&>()))
+ {
+ return lhs.m_it > rhs.m_it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator>=(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() >= std::declval<I&>()))
+ {
+ return lhs.m_it >= rhs.m_it;
+ }
+
+ // This would implement the P2836R1 fix from std::basic_const_iterator,
+ // but a const_iterator on a range<pointer> would again allow us to
+ // mutate the pointed-to object, which is exactly what we want to
+ // prevent.
+ /*
+ template <typename CI, std::enable_if_t<std::is_convertible_v<const Iterator &, CI>, bool> = true>
+ operator CI() const
+ {
+ return CI{m_it};
+ }
+
+ template <typename CI, std::enable_if_t<std::is_convertible_v<Iterator, CI>, bool> = true>
+ operator CI() &&
+ {
+ return CI{std::move(m_it)};
+ }
+ */
+
+ friend bool comparesEqual(const iterator &lhs, const iterator &rhs) noexcept
+ {
+ return lhs.m_it == rhs.m_it;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(iterator)
+
+ Iterator m_it;
+ };
+
+ using value_type = typename iterator::value_type;
+ using difference_type = typename iterator::difference_type;
+
+ friend bool comparesEqual(const RowView &lhs, const RowView &rhs) noexcept
+ {
+ return lhs.m_begin == rhs.m_begin;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(RowView)
+
+ template <typename RHS>
+ bool operator==(const RHS &rhs) const noexcept
+ {
+ return m_begin == QRangeModelDetails::begin(rhs);
+ }
+ template <typename RHS>
+ bool operator!=(const RHS &rhs) const noexcept
+ {
+ return !operator==(rhs);
+ }
+
+ value_type at(difference_type n) const { return *std::next(m_begin, n); }
+
+ iterator begin() const { return iterator{m_begin}; }
+ iterator end() const { return iterator{m_end}; }
+
+ Iterator m_begin;
+ Sentinel m_end;
+};
+
+// Const-in-depth mapping for row types. We do store row types, and they might
+// be move-only, so we return them by const reference.
+template <typename T, typename = void> struct AsConstRow { using type = const T &; };
+// Otherwise the mapping for basic row types is the same as for data.
+template <typename T> struct AsConstRow<T *> : AsConstData<T *> {};
+template <template <typename> typename U, typename T>
+struct AsConstRow<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>> : AsConstData<U<T>> {};
+template <typename T> struct AsConstRow<std::reference_wrapper<T>>
+ : AsConstData<std::reference_wrapper<T>> {};
+
+template <typename T> using if_range = std::enable_if_t<is_range_v<T>, bool>;
+// If the row type is a range, then we assume that the first type is the
+// element type.
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T, Args...>, if_range<R<T, Args...>>>
+{
+ using type = const R<T, Args...> &;
+};
+
+// specialize for range of pointers and smart pointers
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T *, Args...>, if_range<R<T *, Args...>>>
+{
+ using row_type = R<T, Args...>;
+ using const_iterator = typename row_type::const_iterator;
+ using const_row_type = R<asConst_t<T>>;
+ using type = RowView<const_row_type, const_iterator, const_iterator>;
+};
+
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T, Args...>,
+ std::enable_if_t<std::conjunction_v<is_range<R<T, Args...>>, is_any_shared_ptr<T>>>
+>
+{
+ using row_type = R<T, Args...>;
+ using const_iterator = typename row_type::const_iterator;
+ using const_row_type = R<asConst_t<T>>;
+ using type = RowView<const_row_type, const_iterator, const_iterator>;
+};
+
+template <typename T>
+using asConstRow_t = typename AsConstRow<T>::type;
+
+Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index);
+
+template <typename Type>
+static inline Type dataAtIndex(const QModelIndex &index)
+{
+ Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid");
+ QVariant variant = qVariantAtIndex(index);
+
+ if constexpr (std::is_same_v<QVariant, Type>)
+ return variant;
+ else
+ return variant.value<Type>();
+}
+
+template <typename Type>
+static inline Type dataAtIndex(const QModelIndex &index, int role)
+{
+ Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid");
+ QVariant variant = index.data(role);
+
+ if constexpr (std::is_same_v<QVariant, Type>)
+ return variant;
+ else
+ return variant.value<Type>();
+}
+
+template <bool isTree = false>
+struct ParentIndex
+{
+ ParentIndex(const QModelIndex &dummy = {}) { Q_ASSERT(!dummy.isValid()); }
+ QModelIndex root() const { return {}; }
+};
+
+template <>
+struct ParentIndex<true>
+{
+ const QModelIndex m_rootIndex;
+ QModelIndex root() const { return m_rootIndex; }
+};
+
+template <typename Model, typename Impl>
+struct AdapterStorage : ParentIndex<Impl::protocol_traits::is_tree>
+{
+ // If it is, then we can shortcut the model and operate on the container.
+ // Otherwise we have to go through the model's vtable. For now, this is always
+ // the case.
+ static constexpr bool isRangeModel = std::is_same_v<Model, QRangeModel>;
+ static_assert(isRangeModel, "The model must be a QRangeModel (not a subclass).");
+ std::shared_ptr<QRangeModel> m_model;
+
+ template <typename I = Impl, std::enable_if_t<I::protocol_traits::is_tree, bool> = true>
+ explicit AdapterStorage(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root)
+ : ParentIndex<Impl::protocol_traits::is_tree>{root}, m_model(model)
+ {
+ }
+
+ explicit AdapterStorage(Model *model)
+ : m_model{model}
+ {}
+
+ const Impl *implementation() const
+ {
+ return static_cast<const Impl *>(QRangeModelImplBase::getImplementation(m_model.get()));
+ }
+
+ Impl *implementation()
+ {
+ return static_cast<Impl *>(QRangeModelImplBase::getImplementation(m_model.get()));
+ }
+
+ auto *operator->()
+ {
+ if constexpr (isRangeModel)
+ return implementation();
+ else
+ return m_model.get();
+ }
+
+ const auto *operator->() const
+ {
+ if constexpr (isRangeModel)
+ return implementation();
+ else
+ return m_model.get();
+ }
+};
+
+} // QRangeModelDetails
+
+QT_END_NAMESPACE
+
+#endif // Q_QDOC
+
+#endif // QRANGEMODELADAPTER_IMPL_H