diff options
Diffstat (limited to 'src')
38 files changed, 3840 insertions, 95 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/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp index 6531d025bef..89ac917ccd3 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -543,6 +543,30 @@ void examples(QFuture<QString> someQStringFuture, f.cancelChain(); //! [38] } + + { + auto createFuture = [] { return QtFuture::makeReadyVoidFuture(); }; + auto runNestedComputation = [] { return QtFuture::makeReadyVoidFuture(); }; + //! [39] + QFuture<void> nested; + auto f = createFuture() + .then([&]{ + nested = runNestedComputation(); + // do some other work + return nested; + }) + .unwrap() + .then([]{ + // other continuation + }) + .onCanceled([]{ + // handle cancellation + }); + //... + f.cancelChain(); + nested.cancel(); + //! [39] + } } class SomeClass : public QObject 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/global/qtdeprecationmarkers.h b/src/corelib/global/qtdeprecationmarkers.h index 6ebeb3afe89..6a6efe81b9f 100644 --- a/src/corelib/global/qtdeprecationmarkers.h +++ b/src/corelib/global/qtdeprecationmarkers.h @@ -258,6 +258,14 @@ QT_BEGIN_NAMESPACE # define QT_DEPRECATED_VERSION_6_14 #endif +#if QT_WARN_DEPRECATED_UP_TO >= QT_VERSION_CHECK(6, 15, 0) +# define QT_DEPRECATED_VERSION_X_6_15(text) QT_DEPRECATED_X(text) +# define QT_DEPRECATED_VERSION_6_15 QT_DEPRECATED +#else +# define QT_DEPRECATED_VERSION_X_6_15(text) +# define QT_DEPRECATED_VERSION_6_15 +#endif + #define QT_DEPRECATED_VERSION_X_5(minor, text) QT_DEPRECATED_VERSION_X_5_##minor(text) #define QT_DEPRECATED_VERSION_X(major, minor, text) QT_DEPRECATED_VERSION_X_##major##_##minor(text) diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index f533605c721..d72722f063d 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -154,7 +154,7 @@ static bool connectPropertiesHelper(const QModelIndex &index, QObject *item, QOb const QHash<int, QMetaProperty> &properties) { if (!item) - return false; + return true; for (auto &&[role, property] : properties.asKeyValueRange()) { if (property.hasNotifySignal()) { if (!Handler(index, item, context, role, property)) @@ -171,7 +171,7 @@ bool QRangeModelImplBase::connectProperty(const QModelIndex &index, QObject *ite int role, const QMetaProperty &property) { if (!item) - return false; + return true; // nothing to do, continue PropertyChangedHandler handler{index, role}; auto connection = property.enclosingMetaObject()->connect(item, property.notifySignal(), context, std::move(handler)); @@ -199,7 +199,7 @@ bool QRangeModelImplBase::connectPropertyConst(const QModelIndex &index, QObject int role, const QMetaProperty &property) { if (!item) - return false; + return true; // nothing to do, continue ConstPropertyChangedHandler handler{index, role}; if (!property.enclosingMetaObject()->connect(item, property.notifySignal(), context, std::move(handler))) { @@ -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..3e35ad3981c 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); @@ -1687,7 +1715,7 @@ public: bool autoConnectPropertiesInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const { if (!QRangeModelDetails::isValid(row)) - return false; + return true; // nothing to do return forEachColumn(row, rowIndex, parent, [this](const QModelIndex &index, QObject *item) { if constexpr (isMutable()) return Self::connectProperties(index, item, m_data.context, m_data.properties); @@ -2376,6 +2404,7 @@ protected: return that().childRangeImpl(index); } + template <typename, typename, typename> friend class QRangeModelAdapter; ModelData m_data; }; @@ -2409,6 +2438,26 @@ 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)); + } + + bool autoConnectProperties(const QModelIndex &parent) const + { + auto *children = this->childRange(parent); + if (!children) + return true; + return autoConnectPropertiesRange(QRangeModelDetails::refTo(children), parent); + } + protected: QModelIndex indexImpl(int row, int column, const QModelIndex &parent) const { @@ -2641,9 +2690,11 @@ protected: return false; Q_ASSERT(QRangeModelDetails::isValid(row)); const auto &children = this->protocol().childRows(QRangeModelDetails::refTo(row)); - if (!autoConnectPropertiesRange(QRangeModelDetails::refTo(children), - this->itemModel().index(rowIndex, 0, parent))) { - return false; + if (QRangeModelDetails::isValid(children)) { + if (!autoConnectPropertiesRange(QRangeModelDetails::refTo(children), + this->itemModel().index(rowIndex, 0, parent))) { + return false; + } } ++rowIndex; } diff --git a/src/corelib/itemmodels/qrangemodeladapter.h b/src/corelib/itemmodels/qrangemodeladapter.h new file mode 100644 index 00000000000..bd3342e6185 --- /dev/null +++ b/src/corelib/itemmodels/qrangemodeladapter.h @@ -0,0 +1,1671 @@ +// 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 = std::forward<R>(other); + } + this->m_adapter->emitDataChanged(this->m_index, + this->m_index.siblingAtColumn(this->m_adapter->columnCount() - 1)); + if constexpr (Impl::itemsAreQObjects) { + if (this->m_adapter->model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { + impl->autoConnectPropertiesInRow(oldRow, this->m_index.row(), this->m_index.parent()); + if constexpr (is_tree<Impl>) + impl->autoConnectProperties(this->m_index); + } + + } + } + +#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(); + } + } + if constexpr (Impl::itemsAreQObjects) { + if (model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { + const auto begin = QRangeModelDetails::begin(refTo(oldRange)); + const auto end = QRangeModelDetails::end(refTo(oldRange)); + int rowIndex = 0; + for (auto it = begin; it != end; ++it, ++rowIndex) + impl->autoConnectPropertiesInRow(*it, rowIndex, root); + } + } + } + + 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 diff --git a/src/corelib/kernel/qassociativeiterable.cpp b/src/corelib/kernel/qassociativeiterable.cpp index 7c85ce2c10a..8a2fc63c441 100644 --- a/src/corelib/kernel/qassociativeiterable.cpp +++ b/src/corelib/kernel/qassociativeiterable.cpp @@ -7,7 +7,7 @@ QT_BEGIN_NAMESPACE -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED @@ -107,7 +107,7 @@ QVariantConstPointer QAssociativeConstIterator::operator->() const /*! \class QAssociativeIterable - \deprecated [6.13] Use QMetaAssociation::Iterable instead. + \deprecated [6.15] Use QMetaAssociation::Iterable instead. \since 5.2 \inmodule QtCore \brief The QAssociativeIterable class is an iterable interface for an associative container in a QVariant. @@ -273,7 +273,7 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped) /*! \typealias QAssociativeIterable::const_iterator - \deprecated [6.13] Use QMetaAssociation::Iterable::ConstIterator instead. + \deprecated [6.15] Use QMetaAssociation::Iterable::ConstIterator instead. \inmodule QtCore \brief The QAssociativeIterable::const_iterator allows iteration over a container in a QVariant. @@ -286,7 +286,7 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped) /*! \typealias QAssociativeIterable::iterator \since 6.0 - \deprecated [6.13] Use QMetaAssociation::Iterable::Iterator instead. + \deprecated [6.15] Use QMetaAssociation::Iterable::Iterator instead. \inmodule QtCore \brief The QAssociativeIterable::iterator allows iteration over a container in a QVariant. @@ -297,6 +297,6 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped) */ QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) QT_END_NAMESPACE diff --git a/src/corelib/kernel/qassociativeiterable.h b/src/corelib/kernel/qassociativeiterable.h index 02038133fa5..39f66d45fa0 100644 --- a/src/corelib/kernel/qassociativeiterable.h +++ b/src/corelib/kernel/qassociativeiterable.h @@ -9,12 +9,20 @@ QT_BEGIN_NAMESPACE -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300 + // GCC < 13 doesn't accept both deprecation and visibility on the same class + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT +#else + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \ + Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) +#endif + class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaAssociation::Iterable::Iterator instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::Iterator instead.") QAssociativeIterator : public QIterator<QMetaAssociation> { public: @@ -27,15 +35,15 @@ public: : QIterator(std::move(it)) {} - Q_CORE_EXPORT QVariant key() const; - Q_CORE_EXPORT QVariantRef<QAssociativeIterator> value() const; + QVariant key() const; + QVariantRef<QAssociativeIterator> value() const; - Q_CORE_EXPORT QVariantRef<QAssociativeIterator> operator*() const; - Q_CORE_EXPORT QVariantPointer<QAssociativeIterator> operator->() const; + QVariantRef<QAssociativeIterator> operator*() const; + QVariantPointer<QAssociativeIterator> operator->() const; }; class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaAssociation::Iterable::ConstIterator instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::ConstIterator instead.") QAssociativeConstIterator : public QConstIterator<QMetaAssociation> { public: @@ -48,15 +56,15 @@ public: : QConstIterator(std::move(it)) {} - Q_CORE_EXPORT QVariant key() const; - Q_CORE_EXPORT QVariant value() const; + QVariant key() const; + QVariant value() const; - Q_CORE_EXPORT QVariant operator*() const; - Q_CORE_EXPORT QVariantConstPointer operator->() const; + QVariant operator*() const; + QVariantConstPointer operator->() const; }; class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaAssociation::Iterable instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable instead.") QAssociativeIterable : public QIterable<QMetaAssociation> { public: @@ -125,16 +133,16 @@ public: iterator mutableBegin() { return iterator(QIterable::mutableBegin()); } iterator mutableEnd() { return iterator(QIterable::mutableEnd()); } - Q_CORE_EXPORT const_iterator find(const QVariant &key) const; + const_iterator find(const QVariant &key) const; const_iterator constFind(const QVariant &key) const { return find(key); } - Q_CORE_EXPORT iterator mutableFind(const QVariant &key); + iterator mutableFind(const QVariant &key); - Q_CORE_EXPORT bool containsKey(const QVariant &key); - Q_CORE_EXPORT void insertKey(const QVariant &key); - Q_CORE_EXPORT void removeKey(const QVariant &key); + bool containsKey(const QVariant &key); + void insertKey(const QVariant &key); + void removeKey(const QVariant &key); - Q_CORE_EXPORT QVariant value(const QVariant &key) const; - Q_CORE_EXPORT void setValue(const QVariant &key, const QVariant &mapped); + QVariant value(const QVariant &key) const; + void setValue(const QVariant &key, const QVariant &mapped); }; template<> @@ -176,8 +184,10 @@ Q_DECLARE_TYPEINFO(QAssociativeIterable, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QAssociativeIterable::iterator, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QAssociativeIterable::const_iterator, Q_RELOCATABLE_TYPE); +#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15 + QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) QT_END_NAMESPACE diff --git a/src/corelib/kernel/qsequentialiterable.cpp b/src/corelib/kernel/qsequentialiterable.cpp index 5b040404654..b256b129d2c 100644 --- a/src/corelib/kernel/qsequentialiterable.cpp +++ b/src/corelib/kernel/qsequentialiterable.cpp @@ -7,13 +7,13 @@ QT_BEGIN_NAMESPACE -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED /*! \class QSequentialIterable - \deprecated [6.13] Use QMetaSequence::Iterable instead. + \deprecated [6.15] Use QMetaSequence::Iterable instead. \since 5.2 \inmodule QtCore \brief The QSequentialIterable class is an iterable interface for a container in a QVariant. @@ -163,7 +163,7 @@ void QSequentialIterable::set(qsizetype idx, const QVariant &value) /*! \typealias QSequentialIterable::const_iterator - \deprecated [6.13] Use QMetaSequence::Iterable::ConstIterator instead. + \deprecated [6.15] Use QMetaSequence::Iterable::ConstIterator instead. \brief The QSequentialIterable::const_iterator allows iteration over a container in a QVariant. A QSequentialIterable::const_iterator can only be created by a QSequentialIterable instance, @@ -173,7 +173,7 @@ void QSequentialIterable::set(qsizetype idx, const QVariant &value) /*! \typealias QSequentialIterable::iterator \since 6.0 - \deprecated [6.13] Use QMetaSequence::Iterable::Iterator instead. + \deprecated [6.15] Use QMetaSequence::Iterable::Iterator instead. \brief The QSequentialIterable::iterator allows iteration over a container in a QVariant. A QSequentialIterable::iterator can only be created by a QSequentialIterable instance, @@ -225,6 +225,6 @@ QVariantConstPointer QSequentialConstIterator::operator->() const } QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) QT_END_NAMESPACE diff --git a/src/corelib/kernel/qsequentialiterable.h b/src/corelib/kernel/qsequentialiterable.h index 738bebf3631..92252cb19dd 100644 --- a/src/corelib/kernel/qsequentialiterable.h +++ b/src/corelib/kernel/qsequentialiterable.h @@ -9,12 +9,20 @@ QT_BEGIN_NAMESPACE -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300 + // GCC < 13 doesn't accept both deprecation and visibility on the same class + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT +#else + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \ + Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) +#endif + class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaSequence::Iterable::Iterator instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::Iterator instead.") QSequentialIterator : public QIterator<QMetaSequence> { public: @@ -26,12 +34,12 @@ public: : QIterator(std::move(it)) {} - Q_CORE_EXPORT QVariantRef<QSequentialIterator> operator*() const; - Q_CORE_EXPORT QVariantPointer<QSequentialIterator> operator->() const; + QVariantRef<QSequentialIterator> operator*() const; + QVariantPointer<QSequentialIterator> operator->() const; }; class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaSequence::Iterable::ConstIterator instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::ConstIterator instead.") QSequentialConstIterator : public QConstIterator<QMetaSequence> { public: @@ -43,12 +51,12 @@ public: : QConstIterator(std::move(it)) {} - Q_CORE_EXPORT QVariant operator*() const; - Q_CORE_EXPORT QVariantConstPointer operator->() const; + QVariant operator*() const; + QVariantConstPointer operator->() const; }; class -QT_DEPRECATED_VERSION_X_6_13("Use QMetaSequence::Iterable instead.") +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable instead.") QSequentialIterable : public QIterable<QMetaSequence> { public: @@ -118,14 +126,14 @@ public: iterator mutableBegin() { return iterator(QIterable::mutableBegin()); } iterator mutableEnd() { return iterator(QIterable::mutableEnd()); } - Q_CORE_EXPORT QVariant at(qsizetype idx) const; - Q_CORE_EXPORT void set(qsizetype idx, const QVariant &value); + QVariant at(qsizetype idx) const; + void set(qsizetype idx, const QVariant &value); enum Position { Unspecified, AtBegin, AtEnd }; - Q_CORE_EXPORT void addValue(const QVariant &value, Position position = Unspecified); - Q_CORE_EXPORT void removeValue(Position position = Unspecified); + void addValue(const QVariant &value, Position position = Unspecified); + void removeValue(Position position = Unspecified); - Q_CORE_EXPORT QMetaType valueMetaType() const; + QMetaType valueMetaType() const; }; template<> @@ -158,8 +166,10 @@ Q_DECLARE_TYPEINFO(QSequentialIterable, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QSequentialIterable::iterator, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QSequentialIterable::const_iterator, Q_RELOCATABLE_TYPE); +#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15 + QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) QT_END_NAMESPACE diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index d2b2aa1cebb..7cad20e9fd4 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -2852,14 +2852,14 @@ const void *QtPrivate::QVariantTypeCoercer::coerce(const QVariant &value, const return converted.constData(); } -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED /*! \class QVariantRef \since 6.0 - \deprecated [6.13] Use QVariant::Reference instead. + \deprecated [6.15] Use QVariant::Reference instead. \inmodule QtCore \brief The QVariantRef acts as a non-const reference to a QVariant. @@ -2914,7 +2914,7 @@ QT_WARNING_DISABLE_DEPRECATED /*! \class QVariantConstPointer \since 6.0 - \deprecated [6.13] Use QVariant::ConstPointer instead. + \deprecated [6.15] Use QVariant::ConstPointer instead. \inmodule QtCore \brief Emulated const pointer to QVariant based on a pointer. @@ -2952,7 +2952,7 @@ const QVariant *QVariantConstPointer::operator->() const /*! \class QVariantPointer \since 6.0 - \deprecated [6.13] Use QVariant::Pointer instead. + \deprecated [6.15] Use QVariant::Pointer instead. \inmodule QtCore \brief QVariantPointer is a template class that emulates a pointer to QVariant based on a pointer. @@ -2982,7 +2982,7 @@ const QVariant *QVariantConstPointer::operator->() const */ QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) /*! \class QVariant::ConstReference diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 47fb34cbe65..82eec0693d6 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -983,12 +983,12 @@ private: }; } -#if QT_DEPRECATED_SINCE(6, 13) +#if QT_DEPRECATED_SINCE(6, 15) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED template<typename Pointer> class -QT_DEPRECATED_VERSION_X_6_13("Use QVariant::Reference instead.") +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Reference instead.") QVariantRef { private: @@ -1014,7 +1014,7 @@ public: }; class -QT_DEPRECATED_VERSION_X_6_13("Use QVariant::ConstPointer instead.") +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::ConstPointer instead.") QVariantConstPointer { private: @@ -1028,7 +1028,7 @@ public: }; template<typename Pointer> class -QT_DEPRECATED_VERSION_X_6_13("Use QVariant::Pointer instead.") +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Pointer instead.") QVariantPointer { private: @@ -1041,7 +1041,7 @@ public: }; QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 13) +#endif // QT_DEPRECATED_SINCE(6, 15) QT_END_NAMESPACE diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index c4d4daae99f..f3f32e20adc 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -215,6 +215,20 @@ It's recommended to use it on the QFuture object that represents the entire continuation chain, like it's shown in the example above. + If any of the continuations in the chain executes an asynchronous + computation and returns a QFuture representing it, the \c cancelChain() call + will not be propagated into such nested computation once it is started. + The reason for that is that the future will be available in the continuation + chain only when the outer future is fulfilled, but the cancellation might + happen when both an outer and a nested futures are still waiting for their + computations to be finished. In such cases, the nested future needs to be + captured and canceled explicitly. + + \snippet code/src_corelib_thread_qfuture.cpp 39 + + In this example, if \c runNestedComputation() is already in progress, + it can only be canceled by calling \c {nested.cancel()}. + \sa cancel() */ diff --git a/src/gui/accessible/linux/qspimatchrulematcher.cpp b/src/gui/accessible/linux/qspimatchrulematcher.cpp index 48357f7ae63..a63bdd04443 100644 --- a/src/gui/accessible/linux/qspimatchrulematcher.cpp +++ b/src/gui/accessible/linux/qspimatchrulematcher.cpp @@ -37,7 +37,7 @@ QSpiMatchRuleMatcher::QSpiMatchRuleMatcher(const QSpiMatchRule &matchRule) } } - // use qualified interface names to match what accessibleInterfaces() returns + // use qualified interface names to match what AtSpiAdaptor::accessibleInterfaces returns m_interfaces.reserve(matchRule.interfaces.size()); for (const QString &ifaceName : matchRule.interfaces) m_interfaces.push_back("org.a11y.atspi."_L1 + ifaceName); diff --git a/src/gui/math3d/qvectornd.cpp b/src/gui/math3d/qvectornd.cpp index dcd7bdbcf80..ec836cfa56e 100644 --- a/src/gui/math3d/qvectornd.cpp +++ b/src/gui/math3d/qvectornd.cpp @@ -467,7 +467,6 @@ QDataStream &operator>>(QDataStream &stream, QVector2D &vector) float x, y; stream >> x; stream >> y; - Q_ASSERT(qIsFinite(x) && qIsFinite(y)); vector.setX(x); vector.setY(y); return stream; @@ -1098,7 +1097,6 @@ QDataStream &operator>>(QDataStream &stream, QVector3D &vector) stream >> x; stream >> y; stream >> z; - Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z)); vector.setX(x); vector.setY(y); vector.setZ(z); @@ -1627,7 +1625,6 @@ QDataStream &operator>>(QDataStream &stream, QVector4D &vector) stream >> y; stream >> z; stream >> w; - Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z) && qIsFinite(w)); vector.setX(x); vector.setY(y); vector.setZ(z); diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index cb304fc865c..2a15dca0635 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -148,6 +148,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http access/qrestaccessmanager.cpp access/qrestaccessmanager.h access/qrestaccessmanager_p.h access/qrestreply.cpp access/qrestreply.h access/qrestreply_p.h access/qsocketabstraction_p.h + access/qtcpkeepaliveconfiguration_p.h socket/qhttpsocketengine.cpp socket/qhttpsocketengine_p.h ) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index e0f5bfc2d64..1b8bfd5d72b 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -1485,6 +1485,18 @@ void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration ¶m d->http2Parameters = params; } +QTcpKeepAliveConfiguration QHttpNetworkConnection::tcpKeepAliveParameters() const +{ + Q_D(const QHttpNetworkConnection); + return d->tcpKeepAliveConfiguration; +} + +void QHttpNetworkConnection::setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config) +{ + Q_D(QHttpNetworkConnection); + d->tcpKeepAliveConfiguration = config; +} + // SSL support below #ifndef QT_NO_SSL void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config) diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 67b568caea8..f35b89d1aec 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -36,6 +36,7 @@ #include <private/http2protocol_p.h> #include <private/qhttpnetworkconnectionchannel_p.h> +#include <private/qtcpkeepaliveconfiguration_p.h> #include <utility> @@ -98,6 +99,9 @@ public: QHttp2Configuration http2Parameters() const; void setHttp2Parameters(const QHttp2Configuration ¶ms); + QTcpKeepAliveConfiguration tcpKeepAliveParameters() const; + void setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config); + #ifndef QT_NO_SSL void setSslConfiguration(const QSslConfiguration &config); void ignoreSslErrors(int channel = -1); @@ -255,6 +259,8 @@ public: QString peerVerifyName; + QTcpKeepAliveConfiguration tcpKeepAliveConfiguration; + friend class QHttpNetworkConnectionChannel; }; diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index ffd5d8ff333..e427175ce61 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -923,9 +923,19 @@ void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket // not sure yet if it helps, but it makes sense absSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); - int kaIdleOption = qEnvironmentVariableIntegerValue(keepAliveIdleOption).value_or(TCP_KEEPIDLE_DEF); - int kaIntervalOption = qEnvironmentVariableIntegerValue(keepAliveIntervalOption).value_or(TCP_KEEPINTVL_DEF); - int kaCountOption = qEnvironmentVariableIntegerValue(keepAliveCountOption).value_or(TCP_KEEPCNT_DEF); + QTcpKeepAliveConfiguration keepAliveConfig = connection->tcpKeepAliveParameters(); + + auto getKeepAliveValue = [](int configValue, + const char* envName, + int defaultValue) { + if (configValue > 0) + return configValue; + return static_cast<int>(qEnvironmentVariableIntegerValue(envName).value_or(defaultValue)); + }; + + int kaIdleOption = getKeepAliveValue(keepAliveConfig.idleTimeBeforeProbes.count(), keepAliveIdleOption, TCP_KEEPIDLE_DEF); + int kaIntervalOption = getKeepAliveValue(keepAliveConfig.intervalBetweenProbes.count(), keepAliveIntervalOption, TCP_KEEPINTVL_DEF); + int kaCountOption = getKeepAliveValue(keepAliveConfig.probeCount, keepAliveCountOption, TCP_KEEPCNT_DEF); absSocket->setSocketOption(QAbstractSocket::KeepAliveIdleOption, kaIdleOption); absSocket->setSocketOption(QAbstractSocket::KeepAliveIntervalOption, kaIntervalOption); absSocket->setSocketOption(QAbstractSocket::KeepAliveCountOption, kaCountOption); diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index fbbc55dc4a4..82455e96a81 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -327,6 +327,8 @@ void QHttpThreadDelegate::startRequest() || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { httpConnection->setHttp2Parameters(http2Parameters); } + + httpConnection->setTcpKeepAliveParameters(tcpKeepAliveParameters); #ifndef QT_NO_SSL // Set the QSslConfiguration from this QNetworkRequest. if (ssl) diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index 2ce64dc9a17..f179d95ac17 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -34,6 +34,7 @@ #include "qnetworkaccessauthenticationmanager_p.h" #include <QtNetwork/private/http2protocol_p.h> #include <QtNetwork/qhttpheaders.h> +#include "qtcpkeepaliveconfiguration_p.h" #ifndef QT_NO_SSL #include <memory> @@ -91,6 +92,7 @@ public: QString incomingErrorDetail; QHttp1Configuration http1Parameters; QHttp2Configuration http2Parameters; + QTcpKeepAliveConfiguration tcpKeepAliveParameters; protected: // The zerocopy download buffer, if used: diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index f76d79571c3..9a27da00960 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -896,6 +896,9 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq // Propagate Http/2 settings: delegate->http2Parameters = request.http2Configuration(); delegate->http1Parameters = request.http1Configuration(); + delegate->tcpKeepAliveParameters.idleTimeBeforeProbes = request.tcpKeepAliveIdleTimeBeforeProbes(); + delegate->tcpKeepAliveParameters.intervalBetweenProbes = request.tcpKeepAliveIntervalBetweenProbes(); + delegate->tcpKeepAliveParameters.probeCount = request.tcpKeepAliveProbeCount(); if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid()) delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt(); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 5047fc77bd5..d41124f7b14 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -475,6 +475,9 @@ public: decompressedSafetyCheckThreshold = other.decompressedSafetyCheckThreshold; #endif transferTimeout = other.transferTimeout; + idleTimeBeforeProbes = other.idleTimeBeforeProbes; + intervalBetweenProbes = other.intervalBetweenProbes; + probeCount = other.probeCount; } inline bool operator==(const QNetworkRequestPrivate &other) const @@ -491,6 +494,9 @@ public: #endif && transferTimeout == other.transferTimeout && QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders) + && idleTimeBeforeProbes == other.idleTimeBeforeProbes + && intervalBetweenProbes == other.intervalBetweenProbes + && probeCount == other.probeCount; ; // don't compare cookedHeaders } @@ -508,6 +514,9 @@ public: qint64 decompressedSafetyCheckThreshold = 10ll * 1024ll * 1024ll; #endif std::chrono::milliseconds transferTimeout = 0ms; + std::chrono::duration<int> idleTimeBeforeProbes{0}; + std::chrono::duration<int> intervalBetweenProbes{0}; + int probeCount = 0; }; /*! @@ -1035,6 +1044,96 @@ void QNetworkRequest::setDecompressedSafetyCheckThreshold(qint64 threshold) } #endif // QT_CONFIG(http) +/*! + \since 6.11 + + Returns the time the connection needs to remain idle before TCP + starts sending keepalive probes, if the TCP Keepalive functionality has + been turned on. + + \sa setIdleTimeBeforeProbes +*/ + +std::chrono::seconds QNetworkRequest::tcpKeepAliveIdleTimeBeforeProbes() const +{ + return d->idleTimeBeforeProbes; +} + +/*! + \fn void QNetworkRequest::setTcpKeepAliveIdleTimeBeforeProbes(std::chrono::seconds idle) + \since 6.11 + + Sets the time the connection needs to remain idle before TCP starts + sending keepalive probes to be \a idle, if the TCP Keepalive + functionality has been turned on. + + \sa idleTimeBeforeProbes +*/ + +void QNetworkRequest::doSetIdleTimeBeforeProbes(std::chrono::duration<int> seconds) noexcept +{ + d->idleTimeBeforeProbes = seconds; +} + +/*! + \since 6.11 + + Returns the time between individual keepalive probes, if the TCP + Keepalive functionality has been turned on. + + \sa setIntervalBetweenProbes +*/ + +std::chrono::seconds QNetworkRequest::tcpKeepAliveIntervalBetweenProbes() const +{ + return d->intervalBetweenProbes; +} + +/*! + \fn void QNetworkRequest::setTcpKeepAliveIntervalBetweenProbes(std::chrono::seconds interval) + \since 6.11 + + Sets the time between individual keepalive probes to be \a interval, + if the TCP Keepalive functionality has been turned on. + + \sa intervalBetweenProbes +*/ + +void QNetworkRequest::doSetIntervalBetweenProbes(std::chrono::duration<int> seconds) noexcept +{ + d->intervalBetweenProbes = seconds; +} + +/*! + \since 6.11 + + Returns the maximum number of keepalive probes TCP should send before + dropping the connection, if the TCP Keepalive functionality has been + turned on. + + \sa setIntervalBetweenProbes +*/ + +int QNetworkRequest::tcpKeepAliveProbeCount() const +{ + return d->probeCount; +} + +/*! + \since 6.11 + + Sets the maximum number of keepalive \a probes TCP should send + before dropping the connection, if the TCP Keepalive functionality has + been turned on. + + \sa probeCount +*/ + +void QNetworkRequest::setTcpKeepAliveProbeCount(int probes) noexcept +{ + d->probeCount = probes; +} + #if QT_CONFIG(http) || defined (Q_OS_WASM) /*! \fn int QNetworkRequest::transferTimeout() const diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index ea70255a718..49aa45233af 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -7,12 +7,15 @@ #include <QtNetwork/qtnetworkglobal.h> #include <QtNetwork/qhttpheaders.h> + +#include <QtCore/qassert.h> #include <QtCore/QSharedDataPointer> #include <QtCore/QString> #include <QtCore/QUrl> #include <QtCore/QVariant> #include <QtCore/q26numeric.h> +#include <QtCore/q20utility.h> #include <chrono> @@ -178,6 +181,22 @@ public: qint64 decompressedSafetyCheckThreshold() const; void setDecompressedSafetyCheckThreshold(qint64 threshold); #endif // QT_CONFIG(http) + std::chrono::seconds tcpKeepAliveIdleTimeBeforeProbes() const; + void setTcpKeepAliveIdleTimeBeforeProbes(std::chrono::seconds idle) + { + const auto r = q26::saturate_cast<int>(idle.count()); + Q_PRE(q20::cmp_equal(r, idle.count())); + doSetIdleTimeBeforeProbes(std::chrono::duration<int>(r)); + } + std::chrono::seconds tcpKeepAliveIntervalBetweenProbes() const; + void setTcpKeepAliveIntervalBetweenProbes(std::chrono::seconds interval) + { + const auto r = q26::saturate_cast<int>(interval.count()); + Q_PRE(q20::cmp_equal(r, interval.count())); + doSetIntervalBetweenProbes(std::chrono::duration<int>(r)); + } + int tcpKeepAliveProbeCount() const; + void setTcpKeepAliveProbeCount(int probes) noexcept; #if QT_CONFIG(http) || defined (Q_OS_WASM) QT_NETWORK_INLINE_SINCE(6, 8) @@ -189,6 +208,8 @@ public: void setTransferTimeout(std::chrono::milliseconds duration = DefaultTransferTimeout); #endif // QT_CONFIG(http) || defined (Q_OS_WASM) private: + void doSetIdleTimeBeforeProbes(std::chrono::duration<int> idle) noexcept; + void doSetIntervalBetweenProbes(std::chrono::duration<int> interval) noexcept; QSharedDataPointer<QNetworkRequestPrivate> d; friend class QNetworkRequestPrivate; }; diff --git a/src/network/access/qtcpkeepaliveconfiguration_p.h b/src/network/access/qtcpkeepaliveconfiguration_p.h new file mode 100644 index 00000000000..b8bd96666ef --- /dev/null +++ b/src/network/access/qtcpkeepaliveconfiguration_p.h @@ -0,0 +1,50 @@ +// 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 QTCPKEEPALIVECONFIGURATION_P_H +#define QTCPKEEPALIVECONFIGURATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include <QtNetwork/qtnetworkglobal.h> + +#include <chrono> + +QT_BEGIN_NAMESPACE + +struct QTcpKeepAliveConfiguration +{ + std::chrono::duration<int> idleTimeBeforeProbes; + std::chrono::duration<int> intervalBetweenProbes; + int probeCount; + + bool isEqual(const QTcpKeepAliveConfiguration &other) const noexcept + { + return idleTimeBeforeProbes == other.idleTimeBeforeProbes + && intervalBetweenProbes == other.intervalBetweenProbes + && probeCount == other.probeCount; + } + + friend bool operator==(const QTcpKeepAliveConfiguration &lhs, const QTcpKeepAliveConfiguration &rhs) noexcept + { return lhs.isEqual(rhs); } + friend bool operator!=(const QTcpKeepAliveConfiguration &lhs, const QTcpKeepAliveConfiguration &rhs) noexcept + { return !lhs.isEqual(rhs); } + +}; + +Q_DECLARE_TYPEINFO(QTcpKeepAliveConfiguration, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif // QTCPKEEPALIVECONFIGURATION_P_H diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp index 1eed2acde53..757959e5694 100644 --- a/src/plugins/platforms/wasm/qwasmdrag.cpp +++ b/src/plugins/platforms/wasm/qwasmdrag.cpp @@ -17,6 +17,7 @@ #include <QFile> #include <private/qshapedpixmapdndwindow_p.h> +#include <private/qdnd_p.h> #include <functional> #include <string> @@ -173,19 +174,21 @@ void QWasmDrag::onNativeDrop(DragEvent *event) // files, but the browser expects that accepted state is set before any // async calls. event->acceptDrop(); + std::shared_ptr<DragState> dragState = m_dragState; - const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos, + const auto dropCallback = [dragState, wasmWindow, targetWindowPos, actions, mouseButton, modifiers](QMimeData *mimeData) { - - auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); - *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData, + if (mimeData) { + auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); + *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData, targetWindowPos, actions, mouseButton, modifiers); - if (dropResponse->isAccepted()) - m_dragState->dropAction = dropResponse->acceptedAction(); + if (dragState && dropResponse->isAccepted()) + dragState->dropAction = dropResponse->acceptedAction(); - delete mimeData; + delete mimeData; + } }; event->dataTransfer.toMimeDataWithFile(dropCallback); @@ -198,10 +201,28 @@ void QWasmDrag::onNativeDragFinished(DragEvent *event) m_dragState->quitEventLoopClosure(); } +void QWasmDrag::onNativeDragEnter(DragEvent *event) +{ + event->webEvent.call<void>("preventDefault"); + + // Already dragging + if (QDragManager::self() && QDragManager::self()->object()) + return; + + // Event coming from external browser, start a drag + if (m_dragState) + m_dragState->dropAction = event->dropAction; + + QDrag *drag = new QDrag(this); + drag->setMimeData(new QMimeData()); + drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); +} + void QWasmDrag::onNativeDragLeave(DragEvent *event) { event->webEvent.call<void>("preventDefault"); - m_dragState->dropAction = event->dropAction; + if (m_dragState) + m_dragState->dropAction = event->dropAction; event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction); } diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h index e821470c913..5bb8ec66a3c 100644 --- a/src/plugins/platforms/wasm/qwasmdrag.h +++ b/src/plugins/platforms/wasm/qwasmdrag.h @@ -32,6 +32,7 @@ public: void onNativeDrop(DragEvent *event); void onNativeDragStarted(DragEvent *event); void onNativeDragFinished(DragEvent *event); + void onNativeDragEnter(DragEvent *event); void onNativeDragLeave(DragEvent *event); // QPlatformDrag: @@ -40,7 +41,7 @@ public: private: struct DragState; - std::unique_ptr<DragState> m_dragState; + std::shared_ptr<DragState> m_dragState; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h index ef1b6129e3c..07faee3fe4b 100644 --- a/src/plugins/platforms/wasm/qwasmevent.h +++ b/src/plugins/platforms/wasm/qwasmevent.h @@ -23,6 +23,7 @@ enum class EventType { DragEnd, DragOver, DragStart, + DragEnter, DragLeave, Drop, KeyDown, diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 264471794bd..6e8bd46ca58 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -203,6 +203,12 @@ void QWasmWindow::registerEventHandlers() QWasmDrag::instance()->onNativeDragFinished(&dragEvent); } ); + m_dragEnterCallback = QWasmEventHandler(m_window, "dragenter", + [this](emscripten::val event) { + DragEvent dragEvent(EventType::DragEnter, event, window()); + QWasmDrag::instance()->onNativeDragEnter(&dragEvent); + } + ); m_dragLeaveCallback = QWasmEventHandler(m_window, "dragleave", [this](emscripten::val event) { DragEvent dragEvent(EventType::DragLeave, event, window()); diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 87f4d6644c7..ca5c9132ca0 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -195,6 +195,7 @@ private: QWasmEventHandler m_dragStartCallback; QWasmEventHandler m_dragEndCallback; QWasmEventHandler m_dropCallback; + QWasmEventHandler m_dragEnterCallback; QWasmEventHandler m_dragLeaveCallback; QWasmEventHandler m_wheelEventCallback; diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index b4c12ed1a0c..d8e41a753ef 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -856,7 +856,7 @@ void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event) if (event->data.data32[0] && event->data.data32[0] != current_target) return; - const bool dropPossible = event->data.data32[1]; + const bool dropPossible = event->data.data32[1] & 1; setCanDrop(dropPossible); if (dropPossible) { |
