Revision control
Copy as Markdown
// 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 QRANGEMODEL_IMPL_H
#define QRANGEMODEL_IMPL_H
#ifndef Q_QDOC
#ifndef QRANGEMODEL_H
#error Do not include qrangemodel_impl.h directly
#endif
#if 0
#pragma qt_sync_skip_header_check
#pragma qt_sync_stop_processing
#endif
#include <QtCore/qabstractitemmodel.h>
#include <QtCore/qcollator.h>
#include <QtCore/qquasivirtual_impl.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qvariant.h>
#include <QtCore/qmap.h>
#include <QtCore/qscopedvaluerollback.h>
#include <QtCore/qset.h>
#include <QtCore/qregularexpression.h>
#include <QtCore/qvarlengtharray.h>
#include <algorithm>
#include <functional>
#include <iterator>
#include <type_traits>
#include <QtCore/qxptype_traits.h>
#include <tuple>
#include <QtCore/q23utility.h>
QT_BEGIN_NAMESPACE
namespace QRangeModelDetails
{
template <typename T, template <typename...> typename... Templates>
struct is_any_of_impl : std::false_type {};
template <template <typename...> typename Template,
typename... Params,
template <typename...> typename... Templates>
struct is_any_of_impl<Template<Params...>, Template, Templates...> : std::true_type {};
template <typename T,
template <typename...> typename Template,
template <typename...> typename... Templates>
struct is_any_of_impl<T, Template, Templates...> : is_any_of_impl<T, Templates...> {};
template <typename T, template <typename...> typename... Templates>
using is_any_of = is_any_of_impl<std::remove_cv_t<T>, Templates...>;
template <typename T, typename = void>
struct is_validatable : std::false_type {};
template <typename T>
struct is_validatable<T, std::void_t<decltype(*std::declval<T>())>>
: std::is_constructible<bool, T> {};
template <typename T, typename = void>
struct is_smart_ptr : std::false_type {};
template <typename T>
struct is_smart_ptr<T,
std::enable_if_t<std::conjunction_v<
std::is_pointer<decltype(std::declval<T&>().get())>,
std::is_same<decltype(*std::declval<T&>().get()), decltype(*std::declval<T&>())>,
is_validatable<T>
>>>
: std::true_type
{};
// TODO: shouldn't we check is_smart_ptr && !is_copy_constructible && !is_copy_assignable
// to support users-specific ptrs?
template <typename T>
using is_any_unique_ptr = is_any_of<T,
#ifndef QT_NO_SCOPED_POINTER
QScopedPointer,
#endif
std::unique_ptr
>;
template <typename T>
using is_any_shared_ptr = is_any_of<T, std::shared_ptr, QSharedPointer,
QExplicitlySharedDataPointer, QSharedDataPointer>;
template <typename T>
using is_any_owning_ptr = std::disjunction<is_any_shared_ptr<T>, is_any_unique_ptr<T>>;
template <typename T>
using is_owning_or_raw_pointer = std::disjunction<is_any_owning_ptr<T>, std::is_pointer<T>>;
template <typename T>
inline auto pointerTo(T&& t) {
using Type = q20::remove_cvref_t<T>;
if constexpr (is_any_of<Type, std::optional>())
return t ? std::addressof(*std::forward<T>(t)) : nullptr;
else if constexpr (std::is_pointer<Type>())
return t;
else if constexpr (is_smart_ptr<Type>())
return t.get();
else if constexpr (is_any_of<Type, std::reference_wrapper>())
return std::addressof(t.get());
else
return std::addressof(std::forward<T>(t));
}
template <typename T>
struct wrapped_helper
{
using type = std::remove_pointer_t<decltype(QRangeModelDetails::pointerTo(std::declval<T&>()))>;
};
template <>
struct wrapped_helper<void>
{
using type = void;
};
template <typename T>
using wrapped_t = typename QRangeModelDetails::wrapped_helper<T>::type;
template <typename T>
using is_wrapped = std::negation<std::is_same<
QRangeModelDetails::wrapped_t<T>, std::remove_reference_t<T>
>>;
template <typename T, typename = void>
struct tuple_like : std::false_type {};
template <typename T, std::size_t N>
struct tuple_like<std::array<T, N>> : std::false_type {};
template <typename T>
struct tuple_like<T, std::void_t<std::tuple_element_t<0, QRangeModelDetails::wrapped_t<T>>>>
: std::true_type {};
template <typename T>
[[maybe_unused]] static constexpr bool tuple_like_v = tuple_like<T>::value;
template <typename T, typename = void>
struct array_like : std::false_type {};
template <typename T, std::size_t N>
struct array_like<std::array<T, N>> : std::true_type {};
template <typename T, std::size_t N>
struct array_like<T[N]> : std::true_type {};
template <typename T>
[[maybe_unused]] static constexpr bool array_like_v = array_like<T>::value;
template <typename T, typename = void>
struct has_metaobject : std::false_type {};
template <typename T>
struct has_metaobject<T, std::void_t<decltype(QRangeModelDetails::wrapped_t<T>::staticMetaObject)>>
: std::true_type {};
template <typename T>
[[maybe_unused]] static constexpr bool has_metaobject_v = has_metaobject<T>::value;
template <typename T>
inline constexpr bool isValid(const T &t) noexcept
{
if constexpr (std::is_array_v<T>)
return true;
else if constexpr (is_validatable<T>())
return bool(t);
else
return true;
}
template <typename T>
inline decltype(auto) refTo(T&& t) {
Q_ASSERT(QRangeModelDetails::isValid(t));
// it's allowed to move only if the object holds unique ownership of the wrapped data
using Type = q20::remove_cvref_t<T>;
if constexpr (is_any_of<T, std::optional>())
return *std::forward<T>(t); // let std::optional resolve dereferencing
if constexpr (!is_wrapped<Type>() || is_any_unique_ptr<Type>())
return q23::forward_like<T>(*QRangeModelDetails::pointerTo(t));
else
return *QRangeModelDetails::pointerTo(t);
}
template <typename It>
auto key(It&& it) -> decltype(it.key()) { return std::forward<It>(it).key(); }
template <typename It>
auto key(It&& it) -> decltype((it->first)) { return std::forward<It>(it)->first; }
template <typename It>
auto value(It&& it) -> decltype(it.value()) { return std::forward<It>(it).value(); }
template <typename It>
auto value(It&& it) -> decltype((it->second)) { return std::forward<It>(it)->second; }
// use our own, ADL friendly versions of begin/end so that we can overload
// for pointers.
using std::begin;
using std::end;
template <typename C>
static auto adl_begin(C &&c) -> decltype(begin(QRangeModelDetails::refTo(std::forward<C>(c))))
{ return begin(QRangeModelDetails::refTo(std::forward<C>(c))); }
template <typename C>
static auto adl_end(C &&c) -> decltype(end(QRangeModelDetails::refTo(std::forward<C>(c))))
{ return end(QRangeModelDetails::refTo(std::forward<C>(c))); }
template <typename C>
static auto pos(C &&c, int i)
{ return std::next(QRangeModelDetails::adl_begin(std::forward<C>(c)), i); }
// Test if a type is a range, and whether we can modify it using the
// standard C++ container member functions insert, erase, and resize.
// For the sake of QAIM, we cannot modify a range if it holds const data
// even if the range itself is not const; we'd need to initialize new rows
// and columns, and move old row and column data.
template <typename C, typename = void>
struct test_insert : std::false_type {};
template <typename C>
struct test_insert<C, std::void_t<decltype(std::declval<C>().insert(
std::declval<typename C::const_iterator>(),
std::declval<typename C::size_type>(),
std::declval<typename C::value_type>()
))>>
: std::true_type
{};
// Can we insert from another (identical) range? Required to support
// move-only types
template <typename C, typename = void>
struct test_insert_range : std::false_type {};
template <typename C>
struct test_insert_range<C, std::void_t<decltype(std::declval<C&>().insert(
std::declval<typename C::const_iterator&>(),
std::declval<std::move_iterator<typename C::iterator>&>(),
std::declval<std::move_iterator<typename C::iterator>&>()
))>>
: std::true_type
{};
template <typename C, typename = void>
struct test_erase : std::false_type {};
template <typename C>
struct test_erase<C, std::void_t<decltype(std::declval<C>().erase(
std::declval<typename C::const_iterator>(),
std::declval<typename C::const_iterator>()
))>>
: std::true_type
{};
template <typename C, typename = void>
struct test_resize : std::false_type {};
template <typename C>
struct test_resize<C, std::void_t<decltype(std::declval<C>().resize(
std::declval<typename C::size_type>(),
std::declval<typename C::value_type>()
))>>
: std::true_type
{};
// we use std::rotate in moveRows/Columns, which requires the values (which
// might be const if we only get a const iterator) to be swappable, and the
// iterator type to be at least a forward iterator
template <typename It>
using test_rotate = std::conjunction<
std::is_swappable<decltype(*std::declval<It>())>,
std::is_base_of<std::forward_iterator_tag,
typename std::iterator_traits<It>::iterator_category>
>;
template <typename C, typename = void>
struct test_splice : std::false_type {};
template <typename C>
struct test_splice<C, std::void_t<decltype(std::declval<C>().splice(
std::declval<typename C::const_iterator>(),
std::declval<C&>(),
std::declval<typename C::const_iterator>(),
std::declval<typename C::const_iterator>()
))>>
: std::true_type
{};
template <typename C>
static void rotate(C& c, int src, int count, int dst) {
auto& container = QRangeModelDetails::refTo(c);
using Container = std::remove_reference_t<decltype(container)>;
const auto srcBegin = QRangeModelDetails::pos(container, src);
const auto srcEnd = std::next(srcBegin, count);
const auto dstBegin = QRangeModelDetails::pos(container, dst);
if constexpr (test_splice<Container>::value) {
if (dst > src && dst < src + count) // dst must be out of the source range
container.splice(srcBegin, container, dstBegin, srcEnd);
else if (dst != src) // otherwise, std::list gets corrupted
container.splice(dstBegin, container, srcBegin, srcEnd);
} else {
if (src < dst) // moving right
std::rotate(srcBegin, srcEnd, dstBegin);
else // moving left
std::rotate(dstBegin, srcBegin, srcEnd);
}
}
// Test if a type is an associative container that we can use for multi-role
// data, i.e. has a key_type and a mapped_type typedef, and maps from int,
// Qt::ItemDataRole, or QString to QVariant. This excludes std::set (and
// unordered_set), which are not useful for us anyway even though they are
// considered associative containers.
template <typename C, typename = void> struct is_multi_role : std::false_type
{
static constexpr bool int_key = false;
};
template <typename C> // Qt::ItemDataRole -> QVariant, or QString -> QVariant, int -> QVariant
struct is_multi_role<C, std::void_t<typename C::key_type, typename C::mapped_type>>
: std::conjunction<std::disjunction<std::is_same<typename C::key_type, int>,
std::is_same<typename C::key_type, Qt::ItemDataRole>,
std::is_same<typename C::key_type, QString>>,
std::is_same<typename C::mapped_type, QVariant>>
{
static constexpr bool int_key = !std::is_same_v<typename C::key_type, QString>;
};
template <typename C>
[[maybe_unused]]
static constexpr bool is_multi_role_v = is_multi_role<C>::value;
using std::size;
template <typename C, typename = void>
struct test_size : std::false_type {};
template <typename C>
struct test_size<C, std::void_t<decltype(size(std::declval<C&>()))>> : std::true_type {};
template <typename C, typename = void>
struct test_cbegin : std::false_type {};
template <typename C>
struct test_cbegin<C, std::void_t<decltype(QRangeModelDetails::adl_begin(std::declval<const C&>()))>>
: std::true_type
{};
template <typename C, typename = void>
struct range_traits : std::false_type {
static constexpr bool is_mutable = !std::is_const_v<C>;
static constexpr bool has_insert = false;
static constexpr bool has_insert_range = false;
static constexpr bool has_erase = false;
static constexpr bool has_resize = false;
static constexpr bool has_rotate = false;
static constexpr bool has_splice = false;
static constexpr bool has_cbegin = false;
};
template <typename C>
struct range_traits<C, std::void_t<decltype(QRangeModelDetails::adl_begin(std::declval<C&>())),
decltype(QRangeModelDetails::adl_end(std::declval<C&>())),
std::enable_if_t<!QRangeModelDetails::is_multi_role_v<C>>
>> : std::true_type
{
using iterator = decltype(QRangeModelDetails::adl_begin(std::declval<C&>()));
using value_type = std::remove_reference_t<decltype(*std::declval<iterator&>())>;
static constexpr bool is_mutable = !std::is_const_v<C> && !std::is_const_v<value_type>;
static constexpr bool has_insert = test_insert<C>();
static constexpr bool has_insert_range = test_insert_range<C>();
static constexpr bool has_erase = test_erase<C>();
static constexpr bool has_resize = test_resize<C>();
static constexpr bool has_rotate = test_rotate<iterator>();
static constexpr bool has_splice = test_splice<C>();
static constexpr bool has_cbegin = test_cbegin<C>::value;
};
// Specializations for types that look like ranges, but should be
// treated as values.
enum class Mutable { Yes, No };
template <Mutable IsMutable>
struct iterable_value : std::false_type {
static constexpr bool is_mutable = IsMutable == Mutable::Yes;
static constexpr bool has_insert = false;
static constexpr bool has_erase = false;
static constexpr bool has_resize = false;
static constexpr bool has_rotate = false;
static constexpr bool has_splice = false;
static constexpr bool has_cbegin = true;
};
template <> struct range_traits<QByteArray> : iterable_value<Mutable::Yes> {};
template <> struct range_traits<QString> : iterable_value<Mutable::Yes> {};
template <class CharT, class Traits, class Allocator>
struct range_traits<std::basic_string<CharT, Traits, Allocator>> : iterable_value<Mutable::Yes>
{};
// const T * and views are read-only
template <typename T> struct range_traits<const T *> : iterable_value<Mutable::No> {};
template <> struct range_traits<QLatin1StringView> : iterable_value<Mutable::No> {};
template <typename C>
using is_range = range_traits<C>;
template <typename C>
[[maybe_unused]] static constexpr bool is_range_v = is_range<C>();
// Detect an ItemAccess specialization with static read/writeRole members
template <typename T> struct QRangeModelItemAccess;
template <typename T>
struct item_access
{
using ItemType = std::remove_pointer_t<T>;
using ItemAccess = QRangeModelItemAccess<ItemType>;
template <typename Access, typename Test>
using hasReadRole_test = decltype(Access::readRole(std::declval<const Test &>(),
Qt::DisplayRole));
static constexpr bool hasReadRole = qxp::is_detected_v<hasReadRole_test, ItemAccess, ItemType>;
template <typename Access, typename Test>
using hasWriteRole_test = decltype(Access::writeRole(std::declval<Test&>(),
std::declval<QVariant>(), Qt::DisplayRole));
static constexpr bool hasWriteRole = qxp::is_detected_v<hasWriteRole_test, ItemAccess, ItemType>;
template <typename Access, typename Test>
using hasFlags_test = decltype(Access::flags(std::declval<const Test&>()));
static constexpr bool hasFlags = qxp::is_detected_v<hasFlags_test, ItemAccess, ItemType>;
template <typename Access, typename Test>
using hasMimeTypes_test = decltype(Access::mimeTypes());
static constexpr bool hasMimeTypes = qxp::is_detected_v<hasMimeTypes_test, ItemAccess, ItemType>;
template <typename Access, typename Test>
using hasMimeData_test = decltype(Access::mimeData(std::declval<QSpan<const Test>>()));
static constexpr bool hasMimeData = qxp::is_detected_v<hasMimeData_test, ItemAccess, ItemType>;
template <typename Access>
using hasCanDropMimeData_test = decltype(Access::canDropMimeData(
std::declval<const QMimeData *>()
));
static constexpr bool hasCanDropMimeData = qxp::is_detected_v<hasCanDropMimeData_test, ItemAccess>;
template <typename Access, typename Test>
using hasDropMimeData_test = decltype(Access::dropMimeData(
std::declval<const QMimeData *>(),
std::declval<std::back_insert_iterator<std::vector<Test>>>())
);
static constexpr bool hasDropMimeData = qxp::is_detected_v<hasDropMimeData_test,
ItemAccess, ItemType>;
// full versions with all parameters
template <typename Access>
using hasCanDropMimeDataFull_test = decltype(Access::canDropMimeData(
std::declval<const QMimeData *>(), Qt::CopyAction, 0, 0, std::declval<const QModelIndex &>()
));
static constexpr bool hasCanDropMimeDataFull = qxp::is_detected_v<hasCanDropMimeDataFull_test,
ItemAccess>;
template <typename Access, typename Test>
using hasDropMimeDataFull_test = decltype(Access::dropMimeData(
std::declval<const QMimeData *>(),
Qt::CopyAction, 0, 0, std::declval<const QModelIndex &>(),
std::declval<std::back_insert_iterator<std::vector<Test>>>()
));
static constexpr bool hasDropMimeDataFull = qxp::is_detected_v<hasDropMimeDataFull_test,
ItemAccess, ItemType>;
};
// Detect which options are set to override default heuristics. Since
// QRangeModel is not yet defined we need to delay the evaluation.
template <typename T> struct QRangeModelRowOptions;
template <typename T, typename = void>
struct row_category : std::false_type
{
static constexpr bool isMultiRole = item_access<std::remove_pointer_t<T>>::hasReadRole;
};
template <typename T>
struct row_category<T, std::void_t<decltype(QRangeModelRowOptions<T>::rowCategory)>>
: std::true_type
{
static constexpr auto rowCategory = QRangeModelRowOptions<T>::rowCategory;
using RowCategory = decltype(rowCategory);
static constexpr bool isMultiRole = rowCategory == RowCategory::MultiRoleItem;
};
template <typename RowOptions>
using hasHeaderData_test = decltype(RowOptions::headerData(0, Qt::DisplayRole));
template <typename row_type>
static constexpr bool hasHeaderData = qxp::is_detected_v<hasHeaderData_test, QRangeModelRowOptions<row_type>>;
template <typename row_type>
using hasRowFlags_test = decltype(QRangeModelRowOptions<row_type>::flags(std::declval<const row_type &>()));
template <typename row_type>
static constexpr bool hasRowFlags = qxp::is_detected_v<hasRowFlags_test, row_type>;
// drag'n'drop handling
template <typename row_type>
using hasMimeTypes_test = decltype(QRangeModelRowOptions<row_type>::mimeTypes());
template <typename row_type>
static constexpr bool hasMimeTypes = qxp::is_detected_v<hasMimeTypes_test, row_type>;
template <typename row_type>
using hasMimeDataIndexList_test = decltype(QRangeModelRowOptions<row_type>::mimeData(
std::declval<const QModelIndexList &>())
);
template <typename row_type>
static constexpr bool hasMimeDataIndexList = qxp::is_detected_v<hasMimeDataIndexList_test, row_type>;
template <typename row_type>
using hasMimeDataRowSpan_test = decltype(QRangeModelRowOptions<row_type>::mimeData(
// we don't call it with a QSpan, but with a range type. QSpan is a close enough match.
std::declval<QSpan<const row_type>>())
);
template <typename row_type>
static constexpr bool hasMimeDataRowSpan = qxp::is_detected_v<hasMimeDataRowSpan_test, row_type>;
// we allow simplified versions of (can)DropMimeData
template <typename row_type>
using hasCanDropMimeData_test = decltype(QRangeModelRowOptions<row_type>::canDropMimeData(
std::declval<const QMimeData *>()
));
template <typename row_type>
static constexpr bool hasCanDropMimeData = qxp::is_detected_v<hasCanDropMimeData_test, row_type>;
template <typename row_type>
using hasDropMimeData_test = decltype(QRangeModelRowOptions<row_type>::dropMimeData(
std::declval<const QMimeData *>(),
std::declval<std::back_insert_iterator<std::vector<row_type>>>()
));
template <typename row_type>
static constexpr bool hasDropMimeData = qxp::is_detected_v<hasDropMimeData_test, row_type>;
// the full versions get all the parameters
template <typename row_type>
using hasCanDropMimeDataFull_test = decltype(QRangeModelRowOptions<row_type>::canDropMimeData(
std::declval<const QMimeData *>(), Qt::CopyAction, 0, 0, std::declval<const QModelIndex &>()
));
template <typename row_type>
static constexpr bool hasCanDropMimeDataFull = qxp::is_detected_v<hasCanDropMimeDataFull_test,
row_type>;
template <typename row_type>
using hasDropMimeDataFull_test = decltype(QRangeModelRowOptions<row_type>::dropMimeData(
std::declval<const QMimeData *>(),
Qt::CopyAction, 0, 0, std::declval<const QModelIndex &>(),
std::declval<std::back_insert_iterator<std::vector<row_type>>>()
));
template <typename row_type>
static constexpr bool hasDropMimeDataFull = qxp::is_detected_v<hasDropMimeDataFull_test,
row_type>;
// Find out how many fixed elements can be retrieved from a row element.
// main template for simple values and ranges. Specializing for ranges
// is ambiguous with arrays, as they are also ranges
template <typename T, typename = void>
struct row_traits {
static constexpr bool is_range = is_range_v<q20::remove_cvref_t<T>>
&& !row_category<T>::isMultiRole;
// A static size of -1 indicates dynamically sized range
// A static size of 0 indicates that the specified type doesn't
// represent static or dynamic range.
static constexpr int static_size = is_range ? -1 : 0;
using item_type = std::conditional_t<is_range, typename range_traits<T>::value_type, T>;
static constexpr int fixed_size() { return 1; }
static constexpr bool hasMetaObject = false;
static QVariant column_name(int)
{
return {};
}
template <typename C, typename Fn>
static bool for_element_at(C &&container, std::size_t idx, Fn &&fn)
{
if constexpr (is_range)
return std::forward<Fn>(fn)(
*QRangeModelDetails::pos(std::forward<C>(container), int(idx)));
else
return std::forward<Fn>(fn)(std::forward<C>(container));
}
template <typename Fn>
static bool for_each_element(const T &row, const QModelIndex &firstIndex, Fn &&fn)
{
if constexpr (static_size == 0) {
return std::forward<Fn>(fn)(firstIndex, QRangeModelDetails::pointerTo(row));
} else {
int columnIndex = -1;
return std::all_of(QRangeModelDetails::adl_begin(row),
QRangeModelDetails::adl_end(row), [&](const auto &item) {
return std::forward<Fn>(fn)(firstIndex.siblingAtColumn(++columnIndex),
QRangeModelDetails::pointerTo(item));
});
}
}
};
// Specialization for tuple-like semantics (prioritized over metaobject)
template <typename T>
struct row_traits<T, std::enable_if_t<tuple_like_v<T> && !row_category<T>::isMultiRole>>
{
static constexpr std::size_t size64 = std::tuple_size_v<T>;
static_assert(q20::in_range<int>(size64));
static constexpr int static_size = int(size64);
// are the types in a tuple all the same
template <std::size_t ...I>
static constexpr bool allSameTypes(std::index_sequence<I...>)
{
return (std::is_same_v<std::tuple_element_t<0, T>,
std::tuple_element_t<I, T>> && ...);
}
using item_type = std::conditional_t<allSameTypes(std::make_index_sequence<size64>{}),
std::tuple_element_t<0, T>, void>;
static constexpr int fixed_size() { return 0; }
static constexpr bool hasMetaObject = false;
template <typename C, typename F>
static auto for_element_at(C &&container, std::size_t idx, F &&function)
{
using type = q20::remove_cvref_t<QRangeModelDetails::wrapped_t<C>>;
constexpr size_t size = std::tuple_size_v<type>;
Q_ASSERT(idx < size);
return QtPrivate::applyIndexSwitch<size>(idx, [&](auto idxConstant) {
return function(get<idxConstant>(QRangeModelDetails::refTo(std::forward<C>(container))));
});
}
static QVariant column_name(int section)
{
constexpr auto size = std::tuple_size_v<T>;
Q_ASSERT(std::size_t(section) < size);
QVariant result;
QtPrivate::applyIndexSwitch<size>(section, [&result](auto idxConstant) {
using ElementType = std::tuple_element_t<idxConstant.value, T>;
const QMetaType metaType = QMetaType::fromType<
QRangeModelDetails::wrapped_t<ElementType>
>();
if (metaType.isValid())
result = QString::fromUtf8(metaType.name());
});
return result;
}
template <typename Fn, std::size_t ...Is>
static bool forEachTupleElement(const T &row, Fn &&fn, std::index_sequence<Is...>)
{
using std::get;
return (std::forward<Fn>(fn)(QRangeModelDetails::pointerTo(get<Is>(row))) && ...);
}
template <typename Fn>
static bool for_each_element(const T &row, const QModelIndex &firstIndex, Fn &&fn)
{
int column = -1;
return forEachTupleElement(row, [&column, &fn, &firstIndex](const QObject *item){
return std::forward<Fn>(fn)(firstIndex.siblingAtColumn(++column), item);
}, std::make_index_sequence<static_size>());
}
};
// Specialization for C arrays and std::array
template <typename T, std::size_t N>
struct row_traits<std::array<T, N>>
{
static_assert(q20::in_range<int>(N));
static constexpr int static_size = int(N);
using item_type = T;
static constexpr int fixed_size() { return 0; }
static constexpr bool hasMetaObject = false;
template <typename C, typename F>
static auto for_element_at(C &&container, std::size_t idx, F &&function)
{
Q_ASSERT(idx < size(QRangeModelDetails::refTo(std::forward<C>(container))));
return function(QRangeModelDetails::refTo(std::forward<C>(container))[idx]);
}
static QVariant column_name(int section)
{
return section;
}
template <typename Fn>
static bool for_each_element(const std::array<T, N> &row, const QModelIndex &firstIndex, Fn &&fn)
{
int columnIndex = -1;
return std::all_of(QRangeModelDetails::adl_begin(row),
QRangeModelDetails::adl_end(row), [&](const auto &item) {
return std::forward<Fn>(fn)(firstIndex.siblingAtColumn(++columnIndex),
QRangeModelDetails::pointerTo(item));
});
}
};
template <typename T, std::size_t N>
struct row_traits<T[N]> : row_traits<std::array<T, N>> {};
// prioritize tuple-like over metaobject
template <typename T>
struct row_traits<T, std::enable_if_t<has_metaobject_v<T> && !tuple_like_v<T>>>
{
static constexpr int static_size = 0;
using item_type = std::conditional_t<row_category<T>::isMultiRole, T, void>;
static int fixed_size() {
if constexpr (row_category<T>::isMultiRole) {
return 1;
} else {
// Interpret a gadget in a list as a multi-column row item. To make
// a list of multi-role items, wrap it into SingleColumn.
static const int columnCount = []{
const QMetaObject &mo = T::staticMetaObject;
return mo.propertyCount() - mo.propertyOffset();
}();
return columnCount;
}
}
static constexpr bool hasMetaObject = true;
template <typename C, typename F>
static auto for_element_at(C &&container, std::size_t, F &&function)
{
return std::forward<F>(function)(std::forward<C>(container));
}
static QVariant column_name(int section)
{
QVariant result;
if (fixed_size() == 1) {
const QMetaType metaType = QMetaType::fromType<T>();
result = QString::fromUtf8(metaType.name());
} else if (section <= fixed_size()) {
const QMetaProperty prop = T::staticMetaObject.property(
section + T::staticMetaObject.propertyOffset());
result = QString::fromUtf8(prop.name());
}
return result;
}
template <typename Fn>
static bool for_each_element(const T &row, const QModelIndex &firstIndex, Fn &&fn)
{
return std::forward<Fn>(fn)(firstIndex, QRangeModelDetails::pointerTo(row));
}
};
template <typename T, typename = void>
struct item_traits
{
template <typename That>
static QHash<int, QByteArray> roleNames(That *)
{
return That::roleNamesForSimpleType();
}
};
template <>
struct item_traits<void>
{
template <typename That>
static QHash<int, QByteArray> roleNames(That *that)
{
return that->itemModel().QAbstractItemModel::roleNames();
}
};
template <typename T>
struct item_traits<T, std::enable_if_t<QRangeModelDetails::is_multi_role<T>::value>>
: item_traits<void>
{
};
template <typename T>
struct item_traits<T, std::enable_if_t<QRangeModelDetails::has_metaobject_v<T>>>
{
template <typename That>
static QHash<int, QByteArray> roleNames(That *that)
{
return That::roleNamesForMetaObject(that->itemModel(), T::staticMetaObject);
}
};
template <typename T>
[[maybe_unused]] static constexpr int static_size_v =
row_traits<std::remove_cv_t<QRangeModelDetails::wrapped_t<T>>>::static_size;
template <typename Range>
struct ListProtocol
{
using row_type = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type;
template <typename R = row_type>
auto newRow() -> decltype(R{}) { return R{}; }
};
template <typename Range>
struct TableProtocol
{
using row_type = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type;
template <typename R = row_type,
std::enable_if_t<
std::conjunction_v<
std::is_destructible<QRangeModelDetails::wrapped_t<R>>,
is_owning_or_raw_pointer<R>
>,
bool> = true>
auto newRow() -> decltype(R(new QRangeModelDetails::wrapped_t<R>)) {
if constexpr (is_any_of<R, std::shared_ptr>())
return std::make_shared<QRangeModelDetails::wrapped_t<R>>();
else
return R(new QRangeModelDetails::wrapped_t<R>);
}
template <typename R = row_type,
std::enable_if_t<!is_owning_or_raw_pointer<R>::value, bool> = true>
auto newRow() -> decltype(R{}) { return R{}; }
template <typename R = row_type,
std::enable_if_t<std::is_pointer_v<std::remove_reference_t<R>>, bool> = true>
auto deleteRow(R&& row) -> decltype(delete row) { delete row; }
};
template <typename Range,
typename R = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type>
using table_protocol_t = std::conditional_t<static_size_v<R> == 0 && !has_metaobject_v<R>,
ListProtocol<Range>, TableProtocol<Range>>;
// Default tree traversal protocol implementation for row types that have
// the respective member functions. The trailing return type implicitly
// removes those functions that are not available.
template <typename Range>
struct DefaultTreeProtocol : TableProtocol<Range>
{
template <typename R /*wrapped_row_type*/>
auto parentRow(const R& row) const -> decltype(row.parentRow())
{
return row.parentRow();
}
template <typename R /* = wrapped_row_type*/>
auto setParentRow(R &row, R* parent) -> decltype(row.setParentRow(parent))
{
row.setParentRow(parent);
}
template <typename R /* = wrapped_row_type*/>
auto childRows(const R &row) const -> decltype(row.childRows())
{
return row.childRows();
}
template <typename R /* = wrapped_row_type*/>
auto childRows(R &row) -> decltype(row.childRows())
{
return row.childRows();
}
};
template <typename P, typename R>
using protocol_parentRow_test = decltype(std::declval<P&>()
.parentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
template <typename P, typename R>
using protocol_parentRow = qxp::is_detected<protocol_parentRow_test, P, R>;
template <typename P, typename R>
using protocol_childRows_test = decltype(std::declval<P&>()
.childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
template <typename P, typename R>
using protocol_childRows = qxp::is_detected<protocol_childRows_test, P, R>;
template <typename P, typename R>
using protocol_setParentRow_test = decltype(std::declval<P&>()
.setParentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>(),
std::declval<QRangeModelDetails::wrapped_t<R>*>()));
template <typename P, typename R>
using protocol_setParentRow = qxp::is_detected<protocol_setParentRow_test, P, R>;
template <typename P, typename R>
using protocol_mutable_childRows_test = decltype(QRangeModelDetails::refTo(std::declval<P&>()
.childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>())) = {});
template <typename P, typename R>
using protocol_mutable_childRows = qxp::is_detected<protocol_mutable_childRows_test, P, R>;
template <typename P, typename = void>
struct protocol_newRow : std::false_type {};
template <typename P>
struct protocol_newRow<P, std::void_t<decltype(std::declval<P&>().newRow())>>
: std::true_type {};
template <typename P, typename R, typename = void>
struct protocol_deleteRow : std::false_type {};
template <typename P, typename R>
struct protocol_deleteRow<P, R,
std::void_t<decltype(std::declval<P&>().deleteRow(std::declval<R&&>()))>>
: std::true_type {};
template <typename Range,
typename Protocol = DefaultTreeProtocol<Range>,
typename R = typename range_traits<Range>::value_type,
typename = void>
struct is_tree_range : std::false_type {};
template <typename Range, typename Protocol, typename R>
struct is_tree_range<Range, Protocol, R,
std::enable_if_t<std::conjunction_v<
protocol_parentRow<Protocol, R>, protocol_childRows<Protocol, R>>>
> : std::true_type {};
template <typename Range>
using if_table_range = std::enable_if_t<std::conjunction_v<
is_range<QRangeModelDetails::wrapped_t<Range>>,
std::negation<is_tree_range<QRangeModelDetails::wrapped_t<Range>>>
>, bool>;
template <typename Range, typename Protocol = DefaultTreeProtocol<Range>>
using if_tree_range = std::enable_if_t<std::conjunction_v<
is_range<QRangeModelDetails::wrapped_t<Range>>,
is_tree_range<QRangeModelDetails::wrapped_t<Range>,
QRangeModelDetails::wrapped_t<Protocol>>
>, bool>;
template <typename Range, typename Protocol>
struct protocol_traits
{
using protocol = QRangeModelDetails::wrapped_t<Protocol>;
using row = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type;
static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>,
protocol_childRows<protocol, row>>;
static constexpr bool is_list = static_size_v<row> == 0
&& (!has_metaobject_v<row> || row_category<row>::isMultiRole);
static constexpr bool is_table = !is_list && !is_tree;
static constexpr bool has_newRow = protocol_newRow<protocol>();
static constexpr bool has_deleteRow = protocol_deleteRow<protocol, row>();
static constexpr bool has_setParentRow = protocol_setParentRow<protocol, row>();
static constexpr bool has_mutable_childRows = protocol_mutable_childRows<protocol, row>();
static constexpr bool is_default = is_any_of<protocol, ListProtocol, TableProtocol, DefaultTreeProtocol>();
};
// Helpers for drag'n'drop:
// MimeDataEntry gives customisations access to a pair of either a row or
// an item (in form of the underlying type, i.e. unwrapped, as that's what
// customizations specialize RowOptions and ItemAccess for), and the
// corresponding index, with decomposition support for easy iteration.
template <typename Entry>
struct MimeDataEntry
{
using entry_type = q20::remove_cvref_t<Entry>;
using wrapped_entry = QRangeModelDetails::wrapped_t<entry_type>;
template<typename T, typename = void>
struct is_constexpr_default_constructible : std::false_type {};
#ifndef Q_CC_MSVC // MSVC selects this even for non-constexpr types
template<typename T>
struct is_constexpr_default_constructible<T, std::void_t<int(*)[(T(), 1)]>> : std::true_type {};
#endif
bool isValid() const { return QRangeModelDetails::isValid(m_entry); }
const wrapped_entry &entry() const
{
if constexpr (QRangeModelDetails::is_owning_or_raw_pointer<entry_type>()) {
// While we mark null-items or indexes in null-rows as not draggable,
// client code might override that, or explicitly call QRM::mimeData()
// with indexes that point at null-rows or -items.
if (Q_UNLIKELY(!QRangeModelDetails::isValid(m_entry))) {
#ifndef QT_NO_DEBUG
qDebug("QRangeModel::mimeData: null-entry, test with isValid before accessing");
#endif
constexpr bool is_constexpr_default_constructible_v =
is_constexpr_default_constructible<wrapped_entry>::value;
if constexpr (is_constexpr_default_constructible_v) {
Q_CONSTINIT static const wrapped_entry emptyDefault;
return QRangeModelDetails::refTo(emptyDefault);
} else {
// known to cause runtime initialization
static const wrapped_entry emptyDefault;
return QRangeModelDetails::refTo(emptyDefault);
}
}
}
return std::as_const(QRangeModelDetails::refTo(m_entry));
}
const QModelIndex &index() const { return m_index; }
template <std::size_t N>
friend decltype(auto) get(const MimeDataEntry &entry)
{
if constexpr (N == 0)
return entry.entry();
else if constexpr (N == 1)
return entry.index();
}
const Entry &m_entry;
const QModelIndex m_index;
};
} // namespace QRangeModelDetails
QT_END_NAMESPACE
// decomposition protocol
namespace std {
template <typename T>
struct tuple_size<QT_PREPEND_NAMESPACE(QRangeModelDetails::MimeDataEntry<T>)>
: std::integral_constant<std::size_t, 2> {};
template <typename T>
struct tuple_element<0, QT_PREPEND_NAMESPACE(QRangeModelDetails::MimeDataEntry<T>)>
{ using type = QT_PREPEND_NAMESPACE(QRangeModelDetails)::wrapped_t<T>; };
template <typename T>
struct tuple_element<1, QT_PREPEND_NAMESPACE(QRangeModelDetails::MimeDataEntry<T>)>
{ using type = QT_PREPEND_NAMESPACE(QModelIndex); };
} // namespace QRangeModelDetails
QT_BEGIN_NAMESPACE
namespace QRangeModelDetails {
// Helper types for drop-support. Client code populates a sequence of
// dropped things via an insertion iterator, and those get wrapped in a
// DroppedEntry, which allows user code to also specify the position of the
// thing in the target model.
struct DroppedEntryCell {
int m_row;
int m_column;
// implicit conversion from a single int is intentional
Q_IMPLICIT DroppedEntryCell() noexcept : m_row(-1), m_column(-1) {}
Q_IMPLICIT DroppedEntryCell(int row, int column = 0) noexcept
: m_row(row), m_column(column)
{}
friend bool operator==(const DroppedEntryCell &lhs, const DroppedEntryCell &rhs) noexcept
{
return lhs.m_row == rhs.m_row && lhs.m_column == rhs.m_column;
}
friend bool operator!=(const DroppedEntryCell &lhs, const DroppedEntryCell &rhs) noexcept
{
return !(lhs == rhs);
}
};
template <typename Entry>
struct DroppedEntry
{
using Cell = DroppedEntryCell;
// implicit conversion from and to entry is intentional
Q_IMPLICIT DroppedEntry(Entry &&entry)
: m_entry(std::move(entry)), m_cell{-1, -1}
{}
Q_IMPLICIT DroppedEntry(Entry &&entry, Cell cell)
: m_entry(std::move(entry)), m_cell(cell)
{}
// we only move the actual data out
operator Entry&&() && { return std::move(m_entry); }
Entry m_entry;
Cell m_cell;
};
class Q_CORE_EXPORT AutoConnectContext : public QObject
{
Q_DISABLE_COPY_MOVE(AutoConnectContext)
public:
enum class AutoConnectMapping
{
Roles,
Columns,
};
AutoConnectContext(QObject *parent)
: QObject(parent)
{}
~AutoConnectContext() override;
AutoConnectMapping mapping = AutoConnectMapping::Roles;
};
template <bool cacheProperties, bool itemsAreQObjects>
struct PropertyData {
static constexpr bool cachesProperties = false;
void invalidateCaches() {}
};
struct PropertyCache
{
static constexpr bool cachesProperties = true;
mutable QHash<int, QMetaProperty> properties;
void invalidateCaches()
{
properties.clear();
}
protected:
~PropertyCache() = default;
};
template <>
struct PropertyData<true, false> : PropertyCache
{};
struct ConnectionStorage
{
struct Connection {
const QObject *sender;
int role;
friend bool operator==(const Connection &lhs, const Connection &rhs) noexcept
{
return lhs.sender == rhs.sender && lhs.role == rhs.role;
}
friend size_t qHash(const Connection &c, size_t seed) noexcept
{
return qHashMulti(seed, c.sender, c.role);
}
};
AutoConnectContext *context = nullptr;
mutable QSet<Connection> connections;
protected:
~ConnectionStorage() = default;
};
template <>
struct PropertyData<true, true> : PropertyCache, ConnectionStorage
{};
template <>
struct PropertyData<false, true> : PropertyData<false, false>, ConnectionStorage
{
mutable QHash<int, QMetaProperty> properties;
};
// The storage of the model data. We might store it as a pointer, or as a
// (copied- or moved-into) value (or smart pointer). But we always return a
// raw pointer.
template <typename ModelStorage, typename = void>
struct Storage
{
mutable std::remove_const_t<ModelStorage> m_model;
using iterator = decltype(QRangeModelDetails::adl_begin(m_model));
using const_iterator = decltype(QRangeModelDetails::adl_begin(m_model));
};
template <typename ModelStorage>
struct Storage<ModelStorage,
std::void_t<decltype(QRangeModelDetails::adl_begin(std::declval<const ModelStorage&>()))>>
{
ModelStorage m_model;
using iterator = decltype(QRangeModelDetails::adl_begin(m_model));
using const_iterator = decltype(QRangeModelDetails::adl_begin(std::as_const(m_model)));
};
template <typename ModelStorage, typename PropertyStorage>
struct ModelData : Storage<ModelStorage>,
PropertyStorage
{
using WrappedStorage = Storage<QRangeModelDetails::wrapped_t<ModelStorage>>;
using iterator = typename WrappedStorage::iterator;
using const_iterator = typename WrappedStorage::const_iterator;
auto model() { return QRangeModelDetails::pointerTo(this->m_model); }
auto model() const { return QRangeModelDetails::pointerTo(this->m_model); }
template <typename Model = ModelStorage>
ModelData(Model &&model)
: Storage<ModelStorage>{std::forward<Model>(model)}
{}
};
} // namespace QRangeModelDetails
class QRangeModel;
// forward declare so that we can declare friends
template <typename, typename, typename> class QRangeModelAdapter;
class QRangeModelImplBase : public QtPrivate::QQuasiVirtualInterface<QRangeModelImplBase>
{
using Self = QRangeModelImplBase;
using QtPrivate::QQuasiVirtualInterface<Self>::Method;
public:
// keep in sync with QRangeModel::AutoConnectPolicy
enum class AutoConnectPolicy {
None,
Full,
OnRead,
};
// keep in sync with QRangeModel::DropOperation
enum class DropOperation {
DontDrop,
Automatic,
OverwriteAndIgnore,
OverwriteAndExtend,
InsertAsSiblings,
InsertAsChildren,
};
// overridable prototypes (quasi-pure-virtual methods)
void invalidateCaches();
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &data, int role);
bool setData(const QModelIndex &index, const QVariant &data, int role);
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &data);
bool clearItemData(const QModelIndex &index);
bool insertColumns(int column, int count, const QModelIndex &parent);
bool removeColumns(int column, int count, const QModelIndex &parent);
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destParent, int destColumn);
bool insertRows(int row, int count, const QModelIndex &parent);
bool removeRows(int row, int count, const QModelIndex &parent);
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destParent, int destRow);
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex sibling(int row, int column, const QModelIndex &index) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QVariant data(const QModelIndex &index, int role) const;
QMap<int, QVariant> itemData(const QModelIndex &index) const;
inline QHash<int, QByteArray> roleNames() const;
QModelIndex parent(const QModelIndex &child) const;
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const;
void setAutoConnectPolicy();
void interfaceVersion(int &version) const;
void sort(int column, Qt::SortOrder order);
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value,
int hits, Qt::MatchFlags flags) const;
Qt::DropActions adjustSupportedDragActions(Qt::DropActions dragActions);
Qt::DropActions adjustSupportedDropActions(Qt::DropActions dropActions);
QStringList mimeTypes() const;
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent) const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent);
QMimeData *mimeData(const QModelIndexList &indexes) const;
// bindings for overriding
using InvalidateCaches = Method<&Self::invalidateCaches>;
using SetHeaderData = Method<&Self::setHeaderData>;
using SetData = Method<&Self::setData>;
using SetItemData = Method<&Self::setItemData>;
using ClearItemData = Method<&Self::clearItemData>;
using InsertColumns = Method<&Self::insertColumns>;
using RemoveColumns = Method<&Self::removeColumns>;
using MoveColumns = Method<&Self::moveColumns>;
using InsertRows = Method<&Self::insertRows>;
using RemoveRows = Method<&Self::removeRows>;
using MoveRows = Method<&Self::moveRows>;
using Index = Method<&Self::index>;
using Sibling = Method<&Self::sibling>;
using RowCount = Method<&Self::rowCount>;
using ColumnCount = Method<&Self::columnCount>;
using Flags = Method<&Self::flags>;
using HeaderData = Method<&Self::headerData>;
using Data = Method<&Self::data>;
using ItemData = Method<&Self::itemData>;
using RoleNames = Method<&Self::roleNames>;
using Parent = Method<&Self::parent>;
// 6.11
using MultiData = Method<&Self::multiData>;
using SetAutoConnectPolicy = Method<&Self::setAutoConnectPolicy>;
// 6.12
using InterfaceVersion = Method<&Self::interfaceVersion>;
using Sort = Method<&Self::sort>;
using Match = Method<&Self::match>;
using AdjustSupportedDragActions = Method<&Self::adjustSupportedDragActions>;
using AdjustSupportedDropActions = Method<&Self::adjustSupportedDropActions>;
using MimeTypes = Method<&Self::mimeTypes>;
using CanDropMimeData = Method<&Self::canDropMimeData>;
using DropMimeData = Method<&Self::dropMimeData>;
using MimeData = Method<&Self::mimeData>;
template <typename C>
using MethodTemplates = std::tuple<
typename C::Destroy,
typename C::InvalidateCaches,
typename C::SetHeaderData,
typename C::SetData,
typename C::SetItemData,
typename C::ClearItemData,
typename C::InsertColumns,
typename C::RemoveColumns,
typename C::MoveColumns,
typename C::InsertRows,
typename C::RemoveRows,
typename C::MoveRows,
typename C::Index,
typename C::Parent,
typename C::Sibling,
typename C::RowCount,
typename C::ColumnCount,
typename C::Flags,
typename C::HeaderData,
typename C::Data,
typename C::ItemData,
typename C::RoleNames,
typename C::MultiData,
typename C::SetAutoConnectPolicy,
typename C::InterfaceVersion,
typename C::Sort,
typename C::Match,
typename C::AdjustSupportedDragActions,
typename C::AdjustSupportedDropActions,
typename C::MimeTypes,
typename C::CanDropMimeData,
typename C::DropMimeData,
typename C::MimeData
>;
static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model);
static Q_CORE_EXPORT const QRangeModelImplBase *getImplementation(const QRangeModel *model);
private:
friend class QRangeModelPrivate;
friend struct PropertyChangedHandler;
QRangeModel *m_rangeModel;
protected:
explicit QRangeModelImplBase(QRangeModel *itemModel)
: m_rangeModel(itemModel)
{}
inline QModelIndex createIndex(int row, int column, const void *ptr = nullptr) const;
inline QModelIndexList persistentIndexList() const;
inline void changePersistentIndex(const QModelIndex &from, const QModelIndex &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);
inline void endRemoveColumns();
inline bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
const QModelIndex &destParent, int destRow);
inline void endMoveColumns();
inline void beginInsertRows(const QModelIndex &parent, int start, int count);
inline void endInsertRows();
inline void beginRemoveRows(const QModelIndex &parent, int start, int count);
inline void endRemoveRows();
inline bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
const QModelIndex &destParent, int destRow);
inline void endMoveRows();
inline void beginLayoutChange();
inline void endLayoutChange();
inline AutoConnectPolicy autoConnectPolicy() const;
inline static Qt::weak_ordering compareData(const QVariant &lhs, const QVariant &rhs,
const QCollator *collator);
public:
inline QAbstractItemModel &itemModel();
inline const QAbstractItemModel &itemModel() const;
// implemented in qrangemodel.cpp
Q_CORE_EXPORT static QHash<int, QByteArray> roleNamesForMetaObject(const QAbstractItemModel &model,
const QMetaObject &metaObject);
Q_CORE_EXPORT static QHash<int, QByteArray> roleNamesForSimpleType();
protected:
Q_CORE_EXPORT QScopedValueRollback<bool> blockDataChangedDispatch();
Q_CORE_EXPORT static QHash<int, QMetaProperty> roleProperties(const QAbstractItemModel &model,
const QMetaObject &metaObject);
Q_CORE_EXPORT static QHash<int, QMetaProperty> columnProperties(const QMetaObject &metaObject);
Q_CORE_EXPORT static bool connectProperty(const QModelIndex &index, const QObject *item,
QRangeModelDetails::AutoConnectContext *context,
int role, const QMetaProperty &property);
Q_CORE_EXPORT static bool connectPropertyConst(const QModelIndex &index, const QObject *item,
QRangeModelDetails::AutoConnectContext *context,
int role, const QMetaProperty &property);
Q_CORE_EXPORT static bool connectProperties(const QModelIndex &index, const QObject *item,
QRangeModelDetails::AutoConnectContext *context,
const QHash<int, QMetaProperty> &properties);
Q_CORE_EXPORT static bool connectPropertiesConst(const QModelIndex &index, const QObject *item,
QRangeModelDetails::AutoConnectContext *context,
const QHash<int, QMetaProperty> &properties);
Q_CORE_EXPORT int sortRole() const;
Q_CORE_EXPORT const QCollator *sortCollator() const;
Q_CORE_EXPORT QCollator matchCollator() const;
Q_CORE_EXPORT static QVariant convertMatchValue(const QVariant &value, Qt::MatchFlags flags);
Q_CORE_EXPORT static bool matchValue(const QString &itemData, const QVariant &value,
Qt::MatchFlags flags);
Q_CORE_EXPORT static bool matchValue(const QString &itemData, const QVariant &value,
Qt::MatchFlags flags, const QCollator &collator);
static bool matchValue(const QVariant &itemData, const QVariant &value, Qt::MatchFlags flags,
const QCollator &collator)
{
if ((flags & Qt::MatchTypeMask) == Qt::MatchExactly)
return itemData == value;
return matchValue(itemData.toString(), value, flags, collator);
}
Q_CORE_EXPORT bool dropDataOnItem(const QMimeData *data, const QModelIndex &index);
};
template <typename Structure, typename Range,
typename Protocol = QRangeModelDetails::table_protocol_t<Range>>
class QRangeModelImpl
: public QtPrivate::QQuasiVirtualSubclass<QRangeModelImpl<Structure, Range, Protocol>,
QRangeModelImplBase>,
private QtPrivate::CompactStorage<Protocol>
{
public:
using range_type = QRangeModelDetails::wrapped_t<Range>;
using range_features = QRangeModelDetails::range_traits<range_type>;
using row_reference = decltype(*QRangeModelDetails::adl_begin(std::declval<range_type&>()));
using row_type = std::remove_reference_t<row_reference>;
using wrapped_row_type = QRangeModelDetails::wrapped_t<row_type>;
using row_features = QRangeModelDetails::range_traits<wrapped_row_type>;
using row_traits = QRangeModelDetails::row_traits<std::remove_cv_t<wrapped_row_type>>;
using protocol_type = QRangeModelDetails::wrapped_t<Protocol>;
using protocol_traits = QRangeModelDetails::protocol_traits<Range, protocol_type>;
static constexpr bool itemsAreQObjects = std::is_base_of_v<QObject, std::remove_pointer_t<
typename row_traits::item_type>>;
static constexpr bool rowsAreQObjects = std::is_base_of_v<QObject, wrapped_row_type>
&& row_traits::hasMetaObject; // not treated as tuple
using ModelData = QRangeModelDetails::ModelData<std::conditional_t<
std::is_pointer_v<Range>,
Range, std::remove_reference_t<Range>
>,
QRangeModelDetails::PropertyData<QRangeModelDetails::has_metaobject_v<typename row_traits::item_type>,
itemsAreQObjects || rowsAreQObjects
>
>;
using ProtocolStorage = QtPrivate::CompactStorage<Protocol>;
using const_row_reference = decltype(*std::declval<typename ModelData::const_iterator&>());
static_assert(!QRangeModelDetails::is_any_of<range_type, std::optional>() &&
!QRangeModelDetails::is_any_of<row_type, std::optional>(),
"Currently, std::optional is not supported for ranges and rows, as "
"it has range semantics in c++26. Once the required behavior is clarified, "
"std::optional for ranges and rows will be supported.");
protected:
using Self = QRangeModelImpl<Structure, Range, Protocol>;
using Ancestor = QtPrivate::QQuasiVirtualSubclass<Self, QRangeModelImplBase>;
Structure& that() { return static_cast<Structure &>(*this); }
const Structure& that() const { return static_cast<const Structure &>(*this); }
template <typename C>
static constexpr int size(const C &c)
{
if (!QRangeModelDetails::isValid(c))
return 0;
if constexpr (QRangeModelDetails::test_size<C>()) {
using std::size;
return int(size(c));
} else {
#if defined(__cpp_lib_ranges)
using std::ranges::distance;
#else
using std::distance;
#endif
using container_type = std::conditional_t<QRangeModelDetails::range_traits<C>::has_cbegin,
const QRangeModelDetails::wrapped_t<C>,
QRangeModelDetails::wrapped_t<C>>;
container_type& container = const_cast<container_type &>(QRangeModelDetails::refTo(c));
return int(distance(QRangeModelDetails::adl_begin(container),
QRangeModelDetails::adl_end(container)));
}
}
static constexpr int static_row_count = QRangeModelDetails::static_size_v<range_type>;
static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>;
static constexpr bool rows_are_owning_or_raw_pointers =
QRangeModelDetails::is_owning_or_raw_pointer<row_type>();
static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>;
static constexpr bool one_dimensional_range = static_column_count == 0;
auto maybeBlockDataChangedDispatch()
{
if constexpr (itemsAreQObjects || rowsAreQObjects)
return this->blockDataChangedDispatch();
else
return false;
}
// A row might be a value (or range of values), or a pointer.
// row_ptr is always a pointer, and const_row_ptr is a pointer to const.
using row_ptr = wrapped_row_type *;
using const_row_ptr = const wrapped_row_type *;
template <typename T>
static constexpr bool has_metaobject = QRangeModelDetails::has_metaobject_v<
std::remove_pointer_t<std::remove_reference_t<T>>>;
// A iterator type to use as the input iterator with the
// range_type::insert(pos, start, end) overload if available (it is in
// std::vector, but not in QList). Generates a prvalue when dereferenced,
// which then gets moved into the newly constructed row, which allows us to
// implement insertRows() for move-only row types.
struct EmptyRowGenerator
{
using value_type = row_type;
using reference = value_type;
using pointer = value_type *;
using iterator_category = std::input_iterator_tag;
using difference_type = int;
value_type operator*() { return impl->makeEmptyRow(parentRow); }
EmptyRowGenerator &operator++() { ++n; return *this; }
friend bool operator==(const EmptyRowGenerator &lhs, const EmptyRowGenerator &rhs) noexcept
{ return lhs.n == rhs.n; }
friend bool operator!=(const EmptyRowGenerator &lhs, const EmptyRowGenerator &rhs) noexcept
{ return !(lhs == rhs); }
difference_type n = 0;
Structure *impl = nullptr;
const row_ptr parentRow = nullptr;
};
// If we have a move-only row_type and can add/remove rows, then the range
// must have an insert-from-range overload.
static_assert(static_row_count || range_features::has_insert_range
|| std::is_copy_constructible_v<row_type>,
"The range holding a move-only row-type must support insert(pos, start, end)");
using AutoConnectPolicy = typename Ancestor::AutoConnectPolicy;
using DropOperation = typename Ancestor::DropOperation;
public:
static constexpr bool isMutable()
{
return range_features::is_mutable && row_features::is_mutable
&& std::is_reference_v<row_reference>
&& Structure::is_mutable_impl;
}
static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; }
static constexpr bool dynamicColumns() { return static_column_count < 0; }
explicit QRangeModelImpl(Range &&model, Protocol&& protocol, QRangeModel *itemModel)
: Ancestor(itemModel)
, ProtocolStorage{std::forward<Protocol>(protocol)}
, m_data{std::forward<Range>(model)}
{
}
// static interface, called by QRangeModelImplBase
void interfaceVersion(int &versionNumber) const
{
versionNumber = QT_VERSION;
}
void invalidateCaches() { m_data.invalidateCaches(); }
// Not implemented
bool setHeaderData(int , Qt::Orientation , const QVariant &, int ) { return false; }
// actual implementations
QModelIndex index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0 || column >= columnCount(parent)
|| row >= rowCount(parent)) {
return {};
}
return that().indexImpl(row, column, parent);
}
QModelIndex sibling(int row, int column, const QModelIndex &index) const
{
if (row == index.row() && column == index.column())
return index;
// we use indexes at column -1 in drag'n'drop handling to mark full rows
if (column >= this->columnCount({}))
return {};
if (row == index.row())
return this->createIndex(row, column, index.constInternalPointer());
const_row_ptr parentRow = static_cast<const_row_ptr>(index.constInternalPointer());
const auto siblingCount = size(that().childrenOf(parentRow));
if (row < 0 || row >= int(siblingCount))
return {};
return this->createIndex(row, column, parentRow);
}
Qt::ItemFlags flags(const QModelIndex &index) const
{
if (!index.isValid()) {
if constexpr (isMutable())
return Qt::ItemIsDropEnabled;
else
return Qt::NoItemFlags;
}
// try customization
std::optional<Qt::ItemFlags> customFlags = std::nullopt;
if constexpr (QRangeModelDetails::hasRowFlags<wrapped_row_type>) {
const_row_reference row = rowData(index);
if (QRangeModelDetails::isValid(row)) {
customFlags = QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>::flags(
QRangeModelDetails::refTo(row)
);
}
}
readAt(index, [&customFlags](auto &&ref){
Q_UNUSED(ref);
using wrapped_value_type = q20::remove_cvref_t<QRangeModelDetails::wrapped_t<decltype(ref)>>;
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>::hasFlags) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
if (QRangeModelDetails::isValid(ref)) {
customFlags = ItemAccess::flags(QRangeModelDetails::refTo(ref));
return true;
}
}
return false;
});
Qt::ItemFlags f = customFlags ? *customFlags : Structure::defaultFlags();
// adjust custom flags based on what is not possible
if constexpr (!isMutable())
f &= ~(Qt::ItemIsEditable | Qt::ItemIsDropEnabled);
if (index.column())
f |= Qt::ItemNeverHasChildren;
if (customFlags)
return f;
// compute flags ourselves
if (!this->itemModel().mimeTypes().isEmpty()) {
f |= Qt::ItemIsDragEnabled;
if constexpr (isMutable())
f |= Qt::ItemIsDropEnabled;
}
if constexpr (QRangeModelDetails::is_owning_or_raw_pointer<row_type>()) {
// pointer rows might be null
const_row_reference row = rowData(index);
if (!QRangeModelDetails::isValid(row))
f &= ~Qt::ItemIsDragEnabled;
}
if constexpr (isMutable()) {
// Note: Read-only items are still droppable - we can't know here
// whether the model will insert data as new rows or children, or if
// it will overwrite the data of the dropped-on item. So we allow
// dropping on items that are not editable.
if constexpr (row_traits::hasMetaObject) {
if (index.column() < row_traits::fixed_size()) {
const QMetaObject mo = wrapped_row_type::staticMetaObject;
const QMetaProperty prop = mo.property(index.column() + mo.propertyOffset());
if (prop.isWritable())
f |= Qt::ItemIsEditable;
}
} else if constexpr (static_column_count <= 0) {
using item_type = typename row_traits::item_type;
if constexpr (QRangeModelDetails::is_owning_or_raw_pointer<item_type>()) {
// pointer items might be null
if (!readAt(index, [](auto &&i){ return QRangeModelDetails::isValid(i); }))
f &= ~Qt::ItemIsDragEnabled;
}
f |= Qt::ItemIsEditable;
} else if constexpr (std::is_reference_v<row_reference> && !std::is_const_v<row_reference>) {
// we want to know if the elements in the tuple are const; they'd always be, if
// we didn't remove the const of the range first.
const_row_reference row = rowData(index);
row_reference mutableRow = const_cast<row_reference>(row);
if (QRangeModelDetails::isValid(mutableRow)) {
row_traits::for_element_at(mutableRow, index.column(), [&f](auto &&ref){
using target_type = decltype(ref);
if constexpr (QRangeModelDetails::is_owning_or_raw_pointer<target_type>()) {
// pointer items might be null
if (!QRangeModelDetails::isValid(ref))
f &= ~Qt::ItemIsDragEnabled;
}
if constexpr (std::is_const_v<std::remove_reference_t<target_type>>)
f &= ~Qt::ItemIsEditable;
else if constexpr (std::is_lvalue_reference_v<target_type>)
f |= Qt::ItemIsEditable;
});
} else {
// If there's no usable value stored in the row, then we can't
// do anything with this item, except perhaps drop data into it
f &= ~Qt::ItemIsEditable;
}
}
}
return f;
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant result;
if constexpr (QRangeModelDetails::hasHeaderData<wrapped_row_type>) {
if (orientation == Qt::Horizontal) {
result = QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>::headerData(
section, role
);
if (result.isValid())
return result;
}
}
if (role != Qt::DisplayRole || orientation != Qt::Horizontal
|| section < 0 || section >= columnCount({})) {
return this->itemModel().QAbstractItemModel::headerData(section, orientation, role);
}
result = row_traits::column_name(section);
if (!result.isValid())
result = this->itemModel().QAbstractItemModel::headerData(section, orientation, role);
return result;
}
QVariant data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return {};
QModelRoleData result(role);
multiData(index, result);
return std::move(result.data());
}
static constexpr bool isRangeModelRole(int role)
{
return role == Qt::RangeModelDataRole
|| role == Qt::RangeModelAdapterRole;
}
static constexpr bool isPrimaryRole(int role)
{
return role == Qt::DisplayRole || role == Qt::EditRole;
}
QMap<int, QVariant> itemData(const QModelIndex &index) const
{
QMap<int, QVariant> result;
if (index.isValid()) {
// optimisation for items backed by a QMap<int, QVariant> or equivalent
if (!readAt(index, [&result](const auto &value) {
if constexpr (std::is_convertible_v<decltype(value), decltype(result)>) {
result = value;
return true;
}
return false;
})) {
const auto roles = this->itemModel().roleNames().keys();
QVarLengthArray<QModelRoleData, 16> roleDataArray;
roleDataArray.reserve(roles.size());
for (auto role : roles) {
if (isRangeModelRole(role))
continue;
roleDataArray.emplace_back(role);
}
QModelRoleDataSpan roleDataSpan(roleDataArray);
multiData(index, roleDataSpan);
for (QModelRoleData &roleData : roleDataSpan) {
if (roleData.data().isValid())
result[roleData.role()] = std::move(roleData.data());
}
}
}
return result;
}
struct ItemReader
{
template <typename value_type>
bool operator()(const value_type &value) const
{
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
const auto readModelData = [&value](QModelRoleData &roleData){
if (!QRangeModelDetails::isValid(value)) {
roleData.clearData();
return true;
}
const int role = roleData.role();
if (role == Qt::RangeModelDataRole) {
// Qt QML support: "modelData" role returns the entire multi-role item.
// QML can only use raw pointers to QObject (so we unwrap), and gadgets
// only by value (so we take the reference).
if constexpr (std::is_copy_assignable_v<wrapped_value_type>)
roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
else
roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
} else if (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 {
return false;
}
return true;
};
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>::hasReadRole) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
for (auto &roleData : roleDataSpan) {
if (!readModelData(roleData)) {
roleData.setData(ItemAccess::readRole(QRangeModelDetails::refTo(value),
roleData.role()));
}
}
} else if constexpr (multi_role()) {
const auto roleNames = [this]() -> QHash<int, QByteArray> {
Q_UNUSED(this);
if constexpr (!multi_role::int_key)
return that->itemModel().roleNames();
else
return {};
}();
using key_type = typename value_type::key_type;
for (auto &roleData : roleDataSpan) {
const auto &it = [&roleNames, &value, role = roleData.role()]{
Q_UNUSED(roleNames);
if constexpr (multi_role::int_key)
return value.find(key_type(role));
else
return value.find(roleNames.value(role));
}();
if (it != QRangeModelDetails::adl_end(value))
roleData.setData(QRangeModelDetails::value(it));
else
roleData.clearData();
}
} else if constexpr (has_metaobject<value_type>) {
if (row_traits::fixed_size() <= 1) {
for (auto &roleData : roleDataSpan) {
if (!readModelData(roleData)) {
roleData.setData(that->readRole(index, roleData.role(),
QRangeModelDetails::pointerTo(value)));
}
}
} else if (index.column() <= row_traits::fixed_size()) {
for (auto &roleData : roleDataSpan) {
const int role = roleData.role();
if (isPrimaryRole(role)) {
roleData.setData(that->readProperty(index,
QRangeModelDetails::pointerTo(value)));
} else {
roleData.clearData();
}
}
}
} else {
for (auto &roleData : roleDataSpan) {
const int role = roleData.role();
if (isPrimaryRole(role) || isRangeModelRole(role))
roleData.setData(read(value));
else
roleData.clearData();
}
}
return true;
}
const QModelIndex &index;
QModelRoleDataSpan roleDataSpan;
const QRangeModelImpl * const that;
};
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const
{
if (!readAt(index, ItemReader{index, roleDataSpan, this})) {
for (auto &roleData : roleDataSpan)
roleData.clearData();
}
}
bool setData(const QModelIndex &index, const QVariant &data, int role)
{
if (!index.isValid())
return false;
if constexpr (isMutable()) {
auto emitDataChanged = qScopeGuard([this, &index, role]{
Q_EMIT this->dataChanged(index, index,
role == Qt::EditRole || role == Qt::RangeModelDataRole
|| role == Qt::RangeModelAdapterRole
? QList<int>{} : QList<int>{role});
});
// we emit dataChanged at the end, block dispatches from auto-connected properties
[[maybe_unused]] auto dataChangedBlocker = maybeBlockDataChangedDispatch();
const auto writeData = [this, column = index.column(), &data, role](auto &&target) -> bool {
using value_type = q20::remove_cvref_t<decltype(target)>;
using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
if constexpr (std::conjunction_v<QRangeModelDetails::is_any_owning_ptr<value_type>,
std::is_default_constructible<wrapped_value_type>>) {
if (!QRangeModelDetails::isValid(target))
target.reset(new wrapped_value_type);
}
auto setRangeModelDataRole = [&target, &data]{
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>) {
// 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, and we can initialize raw nullptr objects.
if constexpr (isWrapped) {
constexpr bool is_raw_pointer = std::is_pointer_v<value_type>;
if constexpr (!is_raw_pointer && std::is_copy_assignable_v<value_type>) {
if (data.canConvert(targetMetaType)) {
target = data.value<value_type>();
return true;
}
} else if constexpr (is_raw_pointer) {
if (!QRangeModelDetails::isValid(target) && data.canConvert(targetMetaType)) {
target = data.value<value_type>();
return true;
}
} else {
Q_UNUSED(target);
}
}
// Otherwise we have a move-only or polymorph type. fall through to
// error handling.
} else if constexpr (isWrapped) {
if (QRangeModelDetails::isValid(target)) {
auto &targetRef = QRangeModelDetails::refTo(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>();
data.canConvert(mt)) {
targetRef = data.value<wrapped_value_type>();
return true;
} else if (const auto mtp = QMetaType::fromType<wrapped_value_type *>();
data.canConvert(mtp)) {
targetRef = *data.value<wrapped_value_type *>();
return true;
}
}
} else if (targetMetaType == dataMetaType) {
QRangeModelDetails::refTo(target) = data.value<value_type>();
return true;
} else if (dataMetaType.flags() & QMetaType::PointerToGadget) {
QRangeModelDetails::refTo(target) = *data.value<value_type *>();
return true;
}
#ifndef QT_NO_DEBUG
qCritical("Not able to assign %s to %s",
qPrintable(QDebug::toString(data)), targetMetaType.name());
#endif
return false;
};
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>::hasWriteRole) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
if (isRangeModelRole(role))
return setRangeModelDataRole();
return ItemAccess::writeRole(QRangeModelDetails::refTo(target), data, role);
} else if constexpr (has_metaobject<value_type>) {
if (row_traits::fixed_size() <= 1) { // multi-role value
if (isRangeModelRole(role))
return setRangeModelDataRole();
return writeRole(role, QRangeModelDetails::pointerTo(target), data);
} else if (column <= row_traits::fixed_size() // multi-column
&& (isPrimaryRole(role) || isRangeModelRole(role))) {
return writeProperty(column, QRangeModelDetails::pointerTo(target), data);
}
} else if constexpr (multi_role::value) {
Qt::ItemDataRole roleToSet = Qt::ItemDataRole(role);
// If there is an entry for EditRole, overwrite that; otherwise,
// set the entry for DisplayRole.
const auto roleNames = [this]() -> QHash<int, QByteArray> {
Q_UNUSED(this);
if constexpr (!multi_role::int_key)
return this->itemModel().roleNames();
else
return {};
}();
if (role == Qt::EditRole) {
if constexpr (multi_role::int_key) {
if (target.find(roleToSet) == target.end())
roleToSet = Qt::DisplayRole;
} else {
if (target.find(roleNames.value(roleToSet)) == target.end())
roleToSet = Qt::DisplayRole;
}
}
if constexpr (multi_role::int_key)
return write(target[roleToSet], data);
else
return write(target[roleNames.value(roleToSet)], data);
} else if (isPrimaryRole(role) || isRangeModelRole(role)) {
return write(target, data);
}
return false;
};
if (!writeAt(index, writeData)) {
emitDataChanged.dismiss();
return false;
} else if constexpr (itemsAreQObjects || rowsAreQObjects) {
if (isRangeModelRole(role) && this->autoConnectPolicy() == AutoConnectPolicy::Full) {
if (QObject *item = data.value<QObject *>())
Self::connectProperties(index, item, m_data.context, m_data.properties);
}
}
return true;
}
return false;
}
template <typename LHS, typename RHS>
void updateTarget(LHS &org, RHS &©) noexcept
{
if constexpr (std::is_pointer_v<RHS>)
return;
else if constexpr (std::is_assignable_v<LHS, RHS>)
org = std::forward<RHS>(copy);
else
qSwap(org, copy);
}
template <typename LHS, typename RHS>
void updateTarget(LHS *org, RHS &©) noexcept
{
updateTarget(*org, std::forward<RHS>(copy));
}
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &data)
{
if (!index.isValid() || data.isEmpty())
return false;
if constexpr (isMutable()) {
auto emitDataChanged = qScopeGuard([this, &index, &data]{
Q_EMIT this->dataChanged(index, index, data.keys());
});
// we emit dataChanged at the end, block dispatches from auto-connected properties
[[maybe_unused]] auto dataChangedBlocker = maybeBlockDataChangedDispatch();
bool tried = false;
auto writeItemData = [this, &tried, &data](auto &target) -> bool {
Q_UNUSED(this);
using value_type = q20::remove_cvref_t<decltype(target)>;
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
// transactional: if possible, modify a copy and only
// update target if all values from data could be stored.
auto makeCopy = [](const value_type &original){
if constexpr (!std::is_copy_assignable_v<wrapped_value_type>)
return QRangeModelDetails::pointerTo(original); // no transaction support
else if constexpr (std::is_pointer_v<decltype(original)>)
return *original;
else if constexpr (std::is_copy_assignable_v<value_type>)
return original;
else
return QRangeModelDetails::pointerTo(original);
};
const auto roleNames = this->itemModel().roleNames();
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>::hasWriteRole) {
tried = true;
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
const auto roles = roleNames.keys();
auto targetCopy = makeCopy(target);
for (int role : roles) {
if (!ItemAccess::writeRole(QRangeModelDetails::refTo(targetCopy),
data.value(role), role)) {
return false;
}
}
updateTarget(target, std::move(targetCopy));
return true;
} else if constexpr (multi_role()) {
using key_type = typename value_type::key_type;
tried = true;
const auto roleName = [&roleNames](int role) {
return roleNames.value(role);
};
// transactional: only update target if all values from data
// can be stored. Storing never fails with int-keys.
if constexpr (!multi_role::int_key)
{
auto invalid = std::find_if(data.keyBegin(), data.keyEnd(),
[&roleName](int role) { return roleName(role).isEmpty(); }
);
if (invalid != data.keyEnd()) {
#ifndef QT_NO_DEBUG
qWarning("No role name set for %d", *invalid);
#endif
return false;
}
}
for (auto &&[role, value] : data.asKeyValueRange()) {
if constexpr (multi_role::int_key)
target[static_cast<key_type>(role)] = value;
else
target[QString::fromUtf8(roleName(role))] = value;
}
return true;
} else if constexpr (has_metaobject<value_type>) {
if (row_traits::fixed_size() <= 1) {
tried = true;
auto targetCopy = makeCopy(target);
for (auto &&[role, value] : data.asKeyValueRange()) {
if (isRangeModelRole(role))
continue;
if (!writeRole(role, QRangeModelDetails::pointerTo(targetCopy), value)) {
const QByteArray roleName = roleNames.value(role);
#ifndef QT_NO_DEBUG
qWarning("Failed to write value '%s' to role '%s'",
qPrintable(QDebug::toString(value)), roleName.data());
#endif
return false;
}
}
updateTarget(target, std::move(targetCopy));
return true;
}
}
return false;
};
if (!writeAt(index, writeItemData)) {
emitDataChanged.dismiss();
if (!tried)
return this->itemModel().QAbstractItemModel::setItemData(index, data);
}
return true;
}
return false;
}
bool clearItemData(const QModelIndex &index)
{
if (!index.isValid())
return false;
if constexpr (isMutable()) {
auto emitDataChanged = qScopeGuard([this, &index]{
Q_EMIT this->dataChanged(index, index, {});
});
auto clearData = [column = index.column()](auto &&target) {
if constexpr (row_traits::hasMetaObject) {
if (row_traits::fixed_size() <= 1) {
// multi-role object/gadget: reset all properties
return resetProperty(-1, QRangeModelDetails::pointerTo(target));
} else if (column <= row_traits::fixed_size()) {
return resetProperty(column, QRangeModelDetails::pointerTo(target));
}
} else { // normal structs, values, associative containers
target = {};
return true;
}
return false;
};
if (!writeAt(index, clearData)) {
emitDataChanged.dismiss();
return false;
}
return true;
}
return false;
}
QHash<int, QByteArray> roleNames() const
{
// will be 'void' if columns don't all have the same type
using item_type = QRangeModelDetails::wrapped_t<typename row_traits::item_type>;
using item_traits = typename QRangeModelDetails::item_traits<item_type>;
return item_traits::roleNames(this);
}
bool autoConnectPropertiesInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const
{
if (!QRangeModelDetails::isValid(row))
return true; // nothing to do
return row_traits::for_each_element(QRangeModelDetails::refTo(row),
this->itemModel().index(rowIndex, 0, parent),
[this](const QModelIndex &index, const QObject *item) {
if constexpr (isMutable())
return Self::connectProperties(index, item, m_data.context, m_data.properties);
else
return Self::connectPropertiesConst(index, item, m_data.context, m_data.properties);
});
}
void clearConnectionInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const
{
if (!QRangeModelDetails::isValid(row))
return;
row_traits::for_each_element(QRangeModelDetails::refTo(row),
this->itemModel().index(rowIndex, 0, parent),
[this](const QModelIndex &, const QObject *item) {
m_data.connections.removeIf([item](const auto &connection) {
return connection.sender == item;
});
return true;
});
}
void setAutoConnectPolicy()
{
if constexpr (itemsAreQObjects || rowsAreQObjects) {
using item_type = std::remove_pointer_t<typename row_traits::item_type>;
using Mapping = QRangeModelDetails::AutoConnectContext::AutoConnectMapping;
delete m_data.context;
m_data.connections = {};
switch (this->autoConnectPolicy()) {
case AutoConnectPolicy::None:
m_data.context = nullptr;
break;
case AutoConnectPolicy::Full:
m_data.context = new QRangeModelDetails::AutoConnectContext(&this->itemModel());
if constexpr (itemsAreQObjects) {
m_data.properties = QRangeModelImplBase::roleProperties(this->itemModel(),
item_type::staticMetaObject);
m_data.context->mapping = Mapping::Roles;
} else {
m_data.properties = QRangeModelImplBase::columnProperties(wrapped_row_type::staticMetaObject);
m_data.context->mapping = Mapping::Columns;
}
if (!m_data.properties.isEmpty())
that().autoConnectPropertiesImpl();
break;
case AutoConnectPolicy::OnRead:
m_data.context = new QRangeModelDetails::AutoConnectContext(&this->itemModel());
if constexpr (itemsAreQObjects) {
m_data.context->mapping = Mapping::Roles;
} else {
m_data.properties = QRangeModelImplBase::columnProperties(wrapped_row_type::staticMetaObject);
m_data.context->mapping = Mapping::Columns;
}
break;
}
} else {
#ifndef QT_NO_DEBUG
qWarning("All items in the range must be QObject subclasses");
#endif
}
}
struct unordered {
friend constexpr bool operator<(unordered, QtPrivate::CompareAgainstLiteralZero) noexcept
{ return false; }
friend constexpr bool operator>(unordered, QtPrivate::CompareAgainstLiteralZero) noexcept
{ return false; }
};
struct Compare
{
template <typename C, typename LessThan>
using sortMember_test = decltype(std::declval<C&>().sort(std::declval<LessThan &&>()));
static constexpr bool hasSortMember = qxp::is_detected_v<sortMember_test, range_type, Compare>;
template <typename Stringish>
using collatedCompare_test = decltype(
std::declval<const QCollator&>().compare(std::declval<const Stringish&>(),
std::declval<const Stringish&>())
);
template <typename Stringish>
static constexpr bool hasCollatedCompare = qxp::is_detected_v<collatedCompare_test,
Stringish>;
Compare(const QRangeModelImpl *impl, int column, Qt::SortOrder order)
: that(impl), m_index(impl->createIndex(-1, column, nullptr))
, collator(impl->sortCollator()), m_order(order), m_sortRole(impl->sortRole())
{
}
template <typename Item>
auto operator()(const Item &lhs, const Item &rhs) const
{
auto ordering = compare(lhs, rhs);
return m_order == Qt::AscendingOrder ? ordering < 0 : ordering > 0;
}
template <typename Item>
auto compare(const Item &lhs, const Item &rhs) const
{
using value_type = QRangeModelDetails::wrapped_t<Item>;
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
if constexpr (QRangeModelDetails::item_access<value_type>::hasReadRole
|| multi_role() || has_metaobject<value_type>) {
QModelRoleData result(m_sortRole);
// Minor abuse of QModelIndex: the reader needs an index to implement
// lazy auto-connections, but we only have a column. So we construct
// an invalid QModelIndex that carries only that column value. That's
// enough for reading values, and the auto-connection logic skips for
// invalid indexes.
ItemReader reader{m_index, result, that};
Q_ASSERT(!reader.index.isValid());
reader(lhs);
const QVariant lhsVariant = std::move(result.data());
reader(rhs);
const QVariant rhsVariant = std::move(result.data());
return QRangeModelImplBase::compareData(lhsVariant, rhsVariant, collator);
} else if constexpr (std::is_same_v<QVariant, value_type>) {
return QRangeModelImplBase::compareData(lhs, rhs, collator);
} else if constexpr (QtOrderingPrivate::CompareThreeWayTester::hasCompareThreeWay_v
<value_type, value_type>) {
// all types supported by QCollator are also three-way comparable
if constexpr (hasCollatedCompare<value_type>) {
if (collator) {
using ordering = decltype(qCompareThreeWay(lhs, rhs));
int res = collator->compare(lhs, rhs);
if (res < 0)
return ordering::less;
if (res > 0)
return ordering::greater;
return ordering::equal;
}
}
return qCompareThreeWay(lhs, rhs);
} else {
return unordered{};
}
}
bool checkComparable() const
{
return that->readAt(that->index(0, 0, {}), [this](const auto &item){
// before we call std::stable_sort, check that we can compare the
// types we'll ultimately get called with. This doesn't catch cases
// where we end up comparing QVariant, and we cannot make this a
// compile time check as long as readAt etc are not constexpr.
using ordering = decltype(compare(item, item));
if constexpr (std::is_same_v<ordering, unordered>) {
#ifndef QT_NO_DEBUG
const QMetaType itemtype = QMetaType::fromType<QRangeModelDetails::wrapped_t<
q20::remove_cvref_t<decltype(item)>>
>();
qCritical("QRangeModel: Cannot compare items of type %s in column %d!",
itemtype.name(), m_index.column());
#else
Q_UNUSED(this);
#endif
return false;
} else {
return true;
}
});
}
template <typename Item>
static std::optional<bool> compareInvalid(const Item &lhs, const Item &rhs)
{
// invalid data > valid data
if (!QRangeModelDetails::isValid(lhs))
return false;
if (!QRangeModelDetails::isValid(rhs))
return true;
return std::nullopt;
}
const QRangeModelImpl * const that;
const QModelIndex m_index;
const QCollator * const collator;
const Qt::SortOrder m_order;
const int m_sortRole;
};
void sort(int column, Qt::SortOrder order)
{
if constexpr (isMutable() && std::is_swappable_v<row_type>) {
if (rowCount({}) < 2 || column >= columnCount({}))
return;
Compare compare(this, column, order);
if (!compare.checkComparable())
return;
this->beginLayoutChange();
QScopeGuard endLayoutChange([this]{ this->endLayoutChange(); });
that().sortImpl([&compare](const auto &leftRow, const auto &rightRow) {
if (auto anyInvalid = Compare::compareInvalid(leftRow, rightRow))
return *anyInvalid;
return row_traits::for_element_at(leftRow, compare.m_index.column(),
[&rightRow, &compare](const auto &leftItem){
return row_traits::for_element_at(rightRow, compare.m_index.column(),
[&leftItem, &compare](const auto &rightItem){
// Called by std::stable_sort. Since "column" is a runtime value, we
// can't statically assert that lhs and rhs are of the same type.
if constexpr (std::is_same_v<decltype(leftItem), decltype(rightItem)>) {
if (auto anyInvalid = Compare::compareInvalid(leftItem, rightItem))
return *anyInvalid;
return compare(QRangeModelDetails::refTo(leftItem),
QRangeModelDetails::refTo(rightItem));
} else {
Q_UNREACHABLE();
}
return false;
});
});
});
}
}
template <typename LessThan>
void sortSubRange(range_type &range, row_ptr expectedParent, const LessThan &lessThan)
{
auto begin = QRangeModelDetails::adl_begin(range);
auto end = QRangeModelDetails::adl_end(range);
if (begin == end)
return;
QModelIndexList persistentIndexes = this->persistentIndexList();
that().prunePersistentIndexList(persistentIndexes, expectedParent);
if (persistentIndexes.isEmpty()) {
using It = typename range_features::iterator;
constexpr bool is_random_access = std::is_base_of_v<std::random_access_iterator_tag,
typename std::iterator_traits<It>::iterator_category>;
// fast path if we have no persistent indexes: sort the range in place
if constexpr (Compare::hasSortMember) {
range.sort(lessThan);
return;
} else if constexpr (is_random_access) {
std::stable_sort(begin, end, lessThan);
return;
}
}
// slow path: create an indexed version of the range by adding a
// column that records row movements.
struct SortTracker
{
row_type row;
int index;
};
const int rangeSize = size(range);
// Allocate all necessary memory here so that a potential exception
// gets thrown before we have made any modifications.
std::vector<SortTracker> tracked;
tracked.reserve(rangeSize);
std::vector<int> newRows;
newRows.resize(rangeSize);
// move all rows into that index paired with its unsorted position
int row = -1;
for (auto &&it = std::move_iterator(begin); it != std::move_iterator(end); ++it)
tracked.emplace_back(SortTracker{*it, ++row});
// sort the index based on a comparions of the data
std::stable_sort(tracked.begin(), tracked.end(),
[&lessThan](const SortTracker &lhs, const SortTracker &rhs){
return lessThan(lhs.row, rhs.row);
});
// write the values back to the range in (now ordered) sequence,
// and create a mapping from old to new row
auto sorted = std::move_iterator(tracked.begin());
auto write = QRangeModelDetails::adl_begin(range);
qsizetype changedIndexCount = 0;
for (int newIndex = 0; newIndex < rangeSize; ++write, ++sorted, ++newIndex) {
auto &&tracker = *sorted;
changedIndexCount += (tracker.index != newIndex);
*write = std::move(tracker.row);
newRows[tracker.index] = newIndex;
}
// free memory from intermediate vector
tracked.clear();
tracked.shrink_to_fit();
// update relevant persistent model indexes
for (const auto &fromIndex : std::as_const(persistentIndexes)) {
const int newRow = newRows.at(fromIndex.row());
if (fromIndex.row() == newRow)
continue;
const QModelIndex toIndex = that().indexImpl(newRow,
fromIndex.column(),
fromIndex.parent());
this->changePersistentIndex(fromIndex, toIndex);
}
}
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits,
Qt::MatchFlags flags) const
{
QCollator collator = this->matchCollator();
collator.setCaseSensitivity(flags.testFlag(Qt::MatchCaseSensitive) ? Qt::CaseSensitive
: Qt::CaseInsensitive);
return that().matchImpl(start, role,
QRangeModelImplBase::convertMatchValue(value, flags), hits,
flags, collator);
}
bool matchRow(const_row_reference row, const QModelIndex &index, int role, const QVariant &value,
Qt::MatchFlags flags, const QCollator &collator) const
{
const uint matchType = (flags & Qt::MatchTypeMask).toInt();
return row_traits::for_element_at(row, index.column(), [&](const auto &element) {
using value_type = q20::remove_cvref_t<decltype(element)>;
using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>::hasReadRole
|| multi_role() || has_metaobject<value_type>) {
QModelRoleData roleData(role);
ItemReader reader{index, roleData, this};
reader(element);
return QRangeModelImplBase::matchValue(roleData.data(), value, flags, collator);
} else if constexpr (std::is_same_v<wrapped_value_type, QVariant>) {
return QRangeModelImplBase::matchValue(element, value, flags, collator);
} else {
constexpr QMetaType mt = QMetaType::fromType<wrapped_value_type>();
if (mt == value.metaType()) {
if (matchType == Qt::MatchExactly)
return mt.equals(QRangeModelDetails::pointerTo(element), value.constData());
else if constexpr (std::is_same_v<wrapped_value_type, QString>)
return QRangeModelImplBase::matchValue(element, value, flags, collator);
} else {
return QRangeModelImplBase::matchValue(QVariant::fromValue(QRangeModelDetails::refTo(element)),
value, flags, collator);
}
}
return false;
});
}
template <typename InsertFn>
bool doInsertColumns(int column, int count, const QModelIndex &parent, InsertFn insertFn)
{
if (count == 0)
return false;
range_type * const children = childRange(parent);
if (!children)
return false;
this->beginInsertColumns(parent, column, column + count - 1);
for (auto &child : *children) {
auto it = QRangeModelDetails::pos(child, column);
(void)insertFn(QRangeModelDetails::refTo(child), it, count);
}
this->endInsertColumns();
// endInsertColumns emits columnsInserted, at which point clients might
// have populated the new columns with objects (if the columns aren't objects
// themselves).
if constexpr (itemsAreQObjects) {
if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::Full) {
for (int r = 0; r < that().rowCount(parent); ++r) {
for (int c = column; c < column + count; ++c) {
const QModelIndex index = that().index(r, c, parent);
writeAt(index, [this, &index](QObject *item){
return Self::connectProperties(index, item,
m_data.context, m_data.properties);
});
}
}
}
}
return true;
}
bool insertColumns(int column, int count, const QModelIndex &parent)
{
if constexpr (dynamicColumns() && isMutable() && row_features::has_insert) {
return doInsertColumns(column, count, parent, [](auto &row, auto it, int n){
row.insert(it, n, {});
return true;
});
} else {
return false;
}
}
bool removeColumns(int column, int count, const QModelIndex &parent)
{
if constexpr (dynamicColumns() && isMutable() && row_features::has_erase) {
if (column < 0 || column + count > columnCount(parent))
return false;
range_type * const children = childRange(parent);
if (!children)
return false;
if constexpr (itemsAreQObjects) {
if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::OnRead) {
for (int r = 0; r < that().rowCount(parent); ++r) {
for (int c = column; c < column + count; ++c) {
const QModelIndex index = that().index(r, c, parent);
writeAt(index, [this](QObject *item){
m_data.connections.removeIf([item](const auto &connection) {
return connection.sender == item;
});
return true;
});
}
}
}
}
this->beginRemoveColumns(parent, column, column + count - 1);
for (auto &child : *children) {
const auto start = QRangeModelDetails::pos(child, column);
QRangeModelDetails::refTo(child).erase(start, std::next(start, count));
}
this->endRemoveColumns();
return true;
}
return false;
}
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,
const QModelIndex &destParent, int destColumn)
{
// we only support moving columns within the same parent
if (sourceParent != destParent)
return false;
if constexpr (isMutable() && (row_features::has_rotate || row_features::has_splice)) {
if (!Structure::canMoveColumns(sourceParent, destParent))
return false;
if constexpr (dynamicColumns()) {
// we only support ranges as columns, as other types might
// not have the same data type across all columns
range_type * const children = childRange(sourceParent);
if (!children)
return false;
if (!this->beginMoveColumns(sourceParent, sourceColumn, sourceColumn + count - 1,
destParent, destColumn)) {
return false;
}
for (auto &child : *children)
QRangeModelDetails::rotate(child, sourceColumn, count, destColumn);
this->endMoveColumns();
return true;
}
}
return false;
}
template <typename InsertFn>
bool doInsertRows(int row, int count, const QModelIndex &parent, InsertFn &&insertFn)
{
range_type *children = childRange(parent);
if (!children)
return false;
this->beginInsertRows(parent, row, row + count - 1);
row_ptr parentRow = parent.isValid()
? QRangeModelDetails::pointerTo(this->rowData(parent))
: nullptr;
(void)std::forward<InsertFn>(insertFn)(*children, parentRow, row, count);
// fix the parent in all children of the modified row, as the
// references back to the parent might have become invalid.
that().resetParentInChildren(children);
this->endInsertRows();
// endInsertRows emits rowsInserted, at which point clients might
// have populated the new row with objects (if the rows aren't objects
// themselves).
if constexpr (itemsAreQObjects || rowsAreQObjects) {
if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::Full) {
const auto begin = QRangeModelDetails::pos(children, row);
const auto end = std::next(begin, count);
int rowIndex = row;
for (auto it = begin; it != end; ++it, ++rowIndex)
autoConnectPropertiesInRow(*it, rowIndex, parent);
}
}
return true;
}
bool insertRows(int row, int count, const QModelIndex &parent)
{
if constexpr (canInsertRows()) {
return doInsertRows(row, count, parent,
[this](range_type &children, row_ptr parentRow, int r, int n){
EmptyRowGenerator generator{0, &that(), parentRow};
const auto pos = QRangeModelDetails::pos(children, r);
if constexpr (range_features::has_insert_range) {
children.insert(pos, std::move(generator), EmptyRowGenerator{n});
} else if constexpr (rows_are_owning_or_raw_pointers) {
auto start = children.insert(pos, n, nullptr); // MSVC doesn't like row_type{}
std::copy(std::move(generator), EmptyRowGenerator{n}, start);
} else {
children.insert(pos, n, std::move(*generator));
}
return true;
});
} else {
return false;
}
}
bool removeRows(int row, int count, const QModelIndex &parent = {})
{
if constexpr (canRemoveRows()) {
const int prevRowCount = rowCount(parent);
if (row < 0 || row + count > prevRowCount)
return false;
range_type *children = childRange(parent);
if (!children)
return false;
if constexpr (itemsAreQObjects || rowsAreQObjects) {
if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::OnRead) {
const auto begin = QRangeModelDetails::pos(children, row);
const auto end = std::next(begin, count);
int rowIndex = row;
for (auto it = begin; it != end; ++it, ++rowIndex)
clearConnectionInRow(*it, rowIndex, parent);
}
}
this->beginRemoveRows(parent, row, row + count - 1);
[[maybe_unused]] bool callEndRemoveColumns = false;
if constexpr (dynamicColumns()) {
// if we remove the last row in a dynamic model, then we no longer
// know how many columns we should have, so they will be reported as 0.
if (prevRowCount == count) {
if (const int columns = columnCount(parent)) {
callEndRemoveColumns = true;
this->beginRemoveColumns(parent, 0, columns - 1);
}
}
}
{ // erase invalidates iterators
const auto begin = QRangeModelDetails::pos(children, row);
const auto end = std::next(begin, count);
that().deleteRemovedRows(begin, end);
children->erase(begin, end);
}
// fix the parent in all children of the modified row, as the
// references back to the parent might have become invalid.
that().resetParentInChildren(children);
if constexpr (dynamicColumns()) {
if (callEndRemoveColumns) {
Q_ASSERT(columnCount(parent) == 0);
this->endRemoveColumns();
}
}
this->endRemoveRows();
return true;
} else {
return false;
}
}
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destParent, int destRow)
{
if constexpr (isMutable() && (range_features::has_rotate || range_features::has_splice)) {
if (!Structure::canMoveRows(sourceParent, destParent))
return false;
if (sourceParent != destParent) {
return that().moveRowsAcross(sourceParent, sourceRow, count,
destParent, destRow);
}
if (sourceRow == destRow || sourceRow == destRow - 1 || count <= 0
|| sourceRow < 0 || sourceRow + count - 1 >= this->rowCount(sourceParent)
|| destRow < 0 || destRow > this->rowCount(destParent)) {
return false;
}
range_type *source = childRange(sourceParent);
// moving within the same range
if (!this->beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destParent, destRow))
return false;
QRangeModelDetails::rotate(source, sourceRow, count, destRow);
that().resetParentInChildren(source);
this->endMoveRows();
return true;
} else {
return false;
}
}
const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
QModelIndex parent(const QModelIndex &child) const { return that().parentImpl(child); }
int rowCount(const QModelIndex &parent) const { return that().rowCountImpl(parent); }
static constexpr int fixedColumnCount()
{
if constexpr (one_dimensional_range)
return row_traits::fixed_size();
else
return static_column_count;
}
int columnCount(const QModelIndex &parent) const { return that().columnCountImpl(parent); }
void destroy() { delete std::addressof(that()); }
Qt::DropActions adjustSupportedDragActions(Qt::DropActions dragActions) {
if constexpr (!isMutable())
dragActions &= ~Qt::MoveAction;
return dragActions;
}
Qt::DropActions adjustSupportedDropActions(Qt::DropActions dropActions)
{
if constexpr (!isMutable())
dropActions = Qt::IgnoreAction;
return dropActions;
}
QStringList mimeTypes() const
{
using ItemType = QRangeModelDetails::wrapped_t<typename row_traits::item_type>;
if constexpr (QRangeModelDetails::item_access<ItemType>::hasMimeTypes)
return QRangeModelDetails::QRangeModelItemAccess<ItemType>::mimeTypes();
else if constexpr (QRangeModelDetails::hasMimeTypes<wrapped_row_type>)
return QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>::mimeTypes();
else
return this->itemModel().QAbstractItemModel::mimeTypes();
}
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &target) const
{
if constexpr (isMutable()) {
bool canDrop;
using RowOptions = QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>;
using ItemType = QRangeModelDetails::wrapped_t<typename row_traits::item_type>;
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<ItemType>;
if constexpr (QRangeModelDetails::item_access<ItemType>::hasCanDropMimeDataFull) {
canDrop = ItemAccess::canDropMimeData(data, action, row, column, target);
} else if constexpr (QRangeModelDetails::hasCanDropMimeDataFull<wrapped_row_type>) {
canDrop = RowOptions::canDropMimeData(data, action, row, column, target);
} else {
canDrop = this->itemModel().QAbstractItemModel::canDropMimeData(data, action, row,
column, target);
if constexpr (QRangeModelDetails::item_access<ItemType>::hasCanDropMimeData)
canDrop &= ItemAccess::canDropMimeData(data);
else if constexpr (QRangeModelDetails::hasCanDropMimeData<wrapped_row_type>)
canDrop &= RowOptions::canDropMimeData(data);
}
return canDrop;
} else {
return false;
}
}
// orientation == vertical: we drop rows; otherwise we drop individual items
template <Qt::Orientation orient, typename Entry>
bool doDropMimeData(std::vector<QRangeModelDetails::DroppedEntry<Entry>> &droppedEntries,
DropOperation dropOperation, int row, int column, const QModelIndex &target)
{
using DroppedEntry = QRangeModelDetails::DroppedEntry<Entry>;
using Cell = typename DroppedEntry::Cell;
if (dropOperation == DropOperation::DontDrop)
return false;
const bool dropOnTarget = row == -1 && column == -1 && target.isValid();
const QModelIndex parent = dropOperation == DropOperation::InsertAsChildren
? target.siblingAtColumn(0) : target.parent();
Cell lastCell;
if constexpr (orient == Qt::Horizontal)
lastCell = {0, -1};
else
lastCell = {-1, 0};
int bottomRow = -1;
int rightColumn = -1;
// set the target cell for all dropped entries and find the bottom-right
// cell relative to the drop position
int maxColumn = that().columnCount(parent) - 1;
for (auto &droppedEntry : droppedEntries) {
if (droppedEntry.m_cell == Cell{-1, -1}) {
if constexpr (orient == Qt::Horizontal) {
// auto-inserted items fill all columns before moving to
// the next row
droppedEntry.m_cell = {lastCell.m_row, lastCell.m_column + 1};
if (droppedEntry.m_cell.m_column > maxColumn) {
droppedEntry.m_cell.m_column = 0;
++droppedEntry.m_cell.m_row;
}
} else {
droppedEntry.m_cell = {lastCell.m_row + 1, lastCell.m_column};
}
}
lastCell = droppedEntry.m_cell;
bottomRow = std::max(lastCell.m_row, bottomRow);
rightColumn = std::max(lastCell.m_column, rightColumn);
}
if (dropOperation == DropOperation::InsertAsChildren) {
row = rowCount(parent);
column = 0;
} else if (dropOnTarget) {
row = target.row();
column = target.column();
} else {
if (row < 0)
row = rowCount(parent);
// dropping into empty space to the right of a table doesn't widen
if (column < 0)
column = 0;
}
const bool overwrite = dropOperation == DropOperation::OverwriteAndIgnore
|| dropOperation == DropOperation::OverwriteAndExtend;
// Compute if we need more rows, and try to add them. Abort if that fails.
const int overwriteRows = overwrite
? std::min(bottomRow + 1, rowCount(parent) - row)
: 0;
const int newRows = dropOperation == DropOperation::OverwriteAndExtend
? bottomRow - overwriteRows + 1
: (dropOperation == DropOperation::InsertAsChildren
|| dropOperation == DropOperation::InsertAsSiblings)
? bottomRow + 1
: 0;
if (newRows > 0 && !insertRows(row, newRows, parent))
return false;
// Ditto for columns, but InsertAsSiblings/Children only applies to rows
const int overwriteColumns = overwrite
? std::min(rightColumn + 1, columnCount(parent) - column)
: 0;
const int newColumns = dropOperation == DropOperation::OverwriteAndExtend
? rightColumn - overwriteColumns + 1 : 0;
if (newColumns > 0 && !insertColumns(column, newColumns, parent))
return false;
// access the target range
range_type *parentRange = that().childRange(parent);
if (!parentRange)
return false;
range_type &targetRange = *parentRange;
int maxRow = that().rowCount(parent) - 1;
maxColumn = that().columnCount(parent) - 1;
auto begin = std::move_iterator(droppedEntries.begin());
auto end = std::move_iterator(droppedEntries.end());
for (; begin != end; ++begin) {
DroppedEntry droppedEntry = *begin;
const Cell cell = {droppedEntry.m_cell.m_row + row, droppedEntry.m_cell.m_column + column};
if (cell.m_row > maxRow || cell.m_column > maxColumn) // Ignore
continue;
auto writeRow = QRangeModelDetails::pos(targetRange, cell.m_row);
if constexpr (orient == Qt::Vertical) { // complete rows
if constexpr (QRangeModelDetails::is_owning_or_raw_pointer<row_type>()) {
if (!*writeRow)
*writeRow = this->protocol().newRow();
**writeRow = std::move(droppedEntry);
} else {
*writeRow = std::move(droppedEntry);
}
} else {
row_traits::for_element_at(*writeRow, cell.m_column, [&](auto &item){
using item_type = q20::remove_cvref_t<decltype(item)>;
using wrapped_item_type = QRangeModelDetails::wrapped_t<item_type>;
if constexpr (QRangeModelDetails::is_any_owning_ptr<item_type>()) {
if (!QRangeModelDetails::isValid(item))
item.reset(new wrapped_item_type{std::move(droppedEntry)});
else
*item = std::move(droppedEntry);
} else if (!QRangeModelDetails::isValid(item)) {
return false;
} else {
item = std::move(droppedEntry);
}
return true;
});
}
}
that().resetParentInChildren(&targetRange);
const QModelIndex topLeft = index(row, column, parent);
const QModelIndex bottomRight = orient == Qt::Horizontal
? sibling(row + bottomRow, column + rightColumn, topLeft)
: sibling(row + bottomRow, maxColumn, topLeft);
this->dataChanged(topLeft, bottomRight, {});
return true;
}
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &target)
{
if constexpr (isMutable()) {
if (!canDropMimeData(data, action, row, column, target))
return false;
const bool dropOnTarget = row == -1 && column == -1 && target.isValid();
auto automaticDropOption = [=](auto dropResult){
DropOperation dropOperation;
if constexpr (std::is_same_v<bool, decltype(dropResult)>) {
dropOperation = dropResult ? DropOperation::Automatic
: DropOperation::DontDrop;
} else { // it's a QRangeModel::DropOperation
dropOperation = static_cast<DropOperation>(dropResult);
}
if (dropOperation == DropOperation::Automatic) {
if constexpr (!canInsertRows())
dropOperation = DropOperation::OverwriteAndIgnore;
else if (!dropOnTarget)
dropOperation = DropOperation::InsertAsSiblings;
else if (target.siblingAtColumn(0).flags().testFlag(Qt::ItemNeverHasChildren))
dropOperation = DropOperation::OverwriteAndExtend;
else
dropOperation = DropOperation::InsertAsChildren;
}
return dropOperation;
};
using ItemType = QRangeModelDetails::wrapped_t<typename row_traits::item_type>;
if constexpr (QRangeModelDetails::item_access<ItemType>::hasDropMimeDataFull
|| QRangeModelDetails::item_access<ItemType>::hasDropMimeData) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<ItemType>;
using DroppedItem = QRangeModelDetails::DroppedEntry<ItemType>;
std::vector<DroppedItem> droppedItems;
DropOperation dropOperation = automaticDropOption([&]{
auto inserter = std::back_inserter(droppedItems);
if constexpr (QRangeModelDetails::item_access<ItemType>::hasDropMimeDataFull)
return ItemAccess::dropMimeData(data, action, row, column, target, inserter);
else
return ItemAccess::dropMimeData(data, inserter);
}());
if (doDropMimeData<Qt::Horizontal>(droppedItems, dropOperation, row, column, target))
return true;
// fall through to try the default mime type
} else if constexpr (QRangeModelDetails::hasDropMimeDataFull<wrapped_row_type>
|| QRangeModelDetails::hasDropMimeData<wrapped_row_type>) {
using RowOptions = QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>;
using DroppedRow = QRangeModelDetails::DroppedEntry<wrapped_row_type>;
std::vector<DroppedRow> droppedRows;
DropOperation dropOperation = automaticDropOption([&]{
auto inserter = std::back_inserter(droppedRows);
if constexpr (QRangeModelDetails::hasDropMimeDataFull<wrapped_row_type>)
return RowOptions::dropMimeData(data, action, row, column, target, inserter);
else
return RowOptions::dropMimeData(data, inserter);
}());
if (doDropMimeData<Qt::Vertical>(droppedRows, dropOperation, row, column, target))
return true;
}
// default mime type handling: dropping on item -> try to set the data
if (dropOnTarget && that().dropOnItem(data, target))
return true;
}
return false;
}
// A bidirectional-iterator that, given a list of QModelIndex, dereferences
// to a list of rows or items, plus QModelIndex, without copying any data.
// For segments in indexes covering full rows, we skip over the individual
// indexes and give the dereferenced index a column value of -1.
struct MimeDataRowIterator
{
using base_iterator = QModelIndexList::const_iterator;
using difference_type = typename base_iterator::difference_type;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = QRangeModelDetails::MimeDataEntry<const_row_reference>;
using reference [[maybe_unused]] = value_type;
using const_reference = value_type;
using pointer [[maybe_unused]] = void;
MimeDataRowIterator() = default;
MimeDataRowIterator(base_iterator it, base_iterator begin, base_iterator end,
const QRangeModelImpl *model)
: m_it(it), m_begin(begin), m_end(end), m_model(model)
, m_columnCount(model->columnCount({}))
{
updateCurrentIndexFullRow();
}
const_reference operator*() const
{
const QModelIndex &index = *m_it;
return {m_model->rowData(index),
m_currentIndexIsFullRow
? m_model->createIndex(m_it->row(), -1, m_it->internalPointer()) : index};
}
MimeDataRowIterator &operator++() {
if (m_currentIndexIsFullRow)
m_it += m_columnCount;
else
++m_it;
updateCurrentIndexFullRow();
return *this;
}
MimeDataRowIterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
MimeDataRowIterator &operator--() {
--m_it;
m_currentIndexIsFullRow = false;
const int lastColumn = m_columnCount - 1;
if (m_it - m_begin >= lastColumn && m_it->column() == lastColumn) {
const QModelIndex &firstInRow = m_it[-lastColumn];
if (m_it->row() == firstInRow.row()
&& firstInRow.internalPointer() == m_it->internalPointer()) {
m_currentIndexIsFullRow = true;
m_it -= lastColumn;
}
}
return *this;
}
MimeDataRowIterator operator--(int) { auto tmp = *this; --(*this); return tmp; }
MimeDataRowIterator operator-(difference_type n) const
{
auto tmp = *this; tmp.m_it -= n; return tmp;
}
bool operator==(const MimeDataRowIterator &other) const { return m_it == other.m_it; }
bool operator!=(const MimeDataRowIterator &other) const { return m_it != other.m_it; }
private:
void updateCurrentIndexFullRow()
{
m_currentIndexIsFullRow = false;
if (m_it == m_end || m_it->column() || m_end - m_it < m_columnCount)
return;
const QModelIndex &lastInRow = m_it[m_columnCount - 1];
m_currentIndexIsFullRow = lastInRow.row() == m_it->row()
&& lastInRow.internalPointer() == m_it->internalPointer();
}
base_iterator m_it;
base_iterator m_begin;
base_iterator m_end;
const QRangeModelImpl *m_model;
int m_columnCount = 0;
bool m_currentIndexIsFullRow = false;
};
struct MimeDataItemIterator
{
using base_iterator = QModelIndexList::const_iterator;
// row_traits::item_type is wrapped, and void if not the same for all columns
using item_type = std::conditional_t<std::is_void_v<typename row_traits::item_type>,
void *, typename row_traits::item_type>;
using difference_type = typename base_iterator::difference_type;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = QRangeModelDetails::MimeDataEntry<item_type>;
using reference [[maybe_unused]] = value_type;
using const_reference = value_type;
using pointer [[maybe_unused]] = void;
const_reference operator*() const
{
const QModelIndex &index = *m_it;
// pointer to the item as stored, including wrapping
const item_type *pitem = nullptr;
const auto &row = m_model->rowData(index);
if (QRangeModelDetails::isValid(row)) {
row_traits::for_element_at(QRangeModelDetails::refTo(row), index.column(),
[&pitem](const auto &item){
pitem = &item;
return true;
});
}
if constexpr (std::disjunction_v<QRangeModelDetails::is_owning_or_raw_pointer<row_type>,
QRangeModelDetails::is_owning_or_raw_pointer<item_type>>) {
if (Q_UNLIKELY(!QRangeModelDetails::isValid(pitem))) {
// no use in warning about invalid item here, as the user
// can't check for validity without dereferencing the iterator.
constexpr bool is_constexpr_default_constructible_v =
value_type::template is_constexpr_default_constructible<item_type>::value;
if constexpr (is_constexpr_default_constructible_v) {
Q_CONSTINIT static const item_type emptyDefault;
return {emptyDefault, index};
} else {
// known to cause runtime initialization
static const item_type emptyDefault;
return {emptyDefault, index};
}
}
}
// this will decompose to a [wrapped_item_type, QModelIndex]
return {*pitem, index};
}
MimeDataItemIterator &operator++() { ++m_it; return *this; }
MimeDataItemIterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
MimeDataItemIterator &operator--() { --m_it; return *this; }
MimeDataItemIterator operator--(int) { auto tmp = *this; --(*this); return tmp; }
MimeDataItemIterator operator-(difference_type n) const
{
auto tmp = *this; tmp.m_it -= n; return tmp;
}
bool operator==(const MimeDataItemIterator &other) const { return m_it == other.m_it; }
bool operator!=(const MimeDataItemIterator &other) const { return m_it != other.m_it; }
base_iterator m_it;
const QRangeModelImpl *m_model;
};
template <typename Iterator>
struct MimeDataRange {
Iterator begin() const { return m_begin; }
Iterator end() const { return m_end; }
auto rbegin() const { return std::reverse_iterator(m_end); }
auto rend() const { return std::reverse_iterator(m_begin); }
auto first() const { return *m_begin;}
auto last() const { return *(m_end - 1);}
bool isEmpty() const { return m_begin == m_end; }
bool empty() const { return m_begin == m_end; }
Iterator m_begin;
Iterator m_end;
};
QMimeData *mimeData(const QModelIndexList &indexes) const
{
QMimeData *result = nullptr;
using RowOptions = QRangeModelDetails::QRangeModelRowOptions<wrapped_row_type>;
using ItemType = QRangeModelDetails::wrapped_t<typename row_traits::item_type>;
if constexpr (QRangeModelDetails::item_access<ItemType>::hasMimeData) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<ItemType>;
const auto begin = MimeDataItemIterator{indexes.begin(), this};
const auto end = MimeDataItemIterator{indexes.end(), this};
result = ItemAccess::mimeData(MimeDataRange<MimeDataItemIterator>{begin, end});
} else if constexpr (QRangeModelDetails::hasMimeDataRowSpan<wrapped_row_type>) {
const auto begin = MimeDataRowIterator(indexes.begin(), indexes.begin(), indexes.end(), this);
const auto end = MimeDataRowIterator(indexes.end(), indexes.begin(), indexes.end(), this);
result = RowOptions::mimeData(MimeDataRange<MimeDataRowIterator>{begin, end});
} else if constexpr (QRangeModelDetails::hasMimeDataIndexList<wrapped_row_type>) {
result = RowOptions::mimeData(indexes);
}
return result;
}
template <typename BaseMethod, typename BaseMethod::template Overridden<Self> overridden>
using Override = typename Ancestor::template Override<BaseMethod, overridden>;
using Destroy = Override<QRangeModelImplBase::Destroy, &Self::destroy>;
using Index = Override<QRangeModelImplBase::Index, &Self::index>;
using Parent = Override<QRangeModelImplBase::Parent, &Self::parent>;
using Sibling = Override<QRangeModelImplBase::Sibling, &Self::sibling>;
using RowCount = Override<QRangeModelImplBase::RowCount, &Self::rowCount>;
using ColumnCount = Override<QRangeModelImplBase::ColumnCount, &Self::columnCount>;
using Flags = Override<QRangeModelImplBase::Flags, &Self::flags>;
using HeaderData = Override<QRangeModelImplBase::HeaderData, &Self::headerData>;
using Data = Override<QRangeModelImplBase::Data, &Self::data>;
using ItemData = Override<QRangeModelImplBase::ItemData, &Self::itemData>;
using RoleNames = Override<QRangeModelImplBase::RoleNames, &Self::roleNames>;
using InvalidateCaches = Override<QRangeModelImplBase::InvalidateCaches, &Self::invalidateCaches>;
using SetHeaderData = Override<QRangeModelImplBase::SetHeaderData, &Self::setHeaderData>;
using SetData = Override<QRangeModelImplBase::SetData, &Self::setData>;
using SetItemData = Override<QRangeModelImplBase::SetItemData, &Self::setItemData>;
using ClearItemData = Override<QRangeModelImplBase::ClearItemData, &Self::clearItemData>;
using InsertColumns = Override<QRangeModelImplBase::InsertColumns, &Self::insertColumns>;
using RemoveColumns = Override<QRangeModelImplBase::RemoveColumns, &Self::removeColumns>;
using MoveColumns = Override<QRangeModelImplBase::MoveColumns, &Self::moveColumns>;
using InsertRows = Override<QRangeModelImplBase::InsertRows, &Self::insertRows>;
using RemoveRows = Override<QRangeModelImplBase::RemoveRows, &Self::removeRows>;
using MoveRows = Override<QRangeModelImplBase::MoveRows, &Self::moveRows>;
using MultiData = Override<QRangeModelImplBase::MultiData, &Self::multiData>;
using SetAutoConnectPolicy = Override<QRangeModelImplBase::SetAutoConnectPolicy,
&Self::setAutoConnectPolicy>;
using InterfaceVersion = Override<QRangeModelImplBase::InterfaceVersion, &Self::interfaceVersion>;
using Sort = Override<QRangeModelImplBase::Sort, &Self::sort>;
using Match = Override<QRangeModelImplBase::Match, &Self::match>;
using AdjustSupportedDragActions = Override<QRangeModelImplBase::AdjustSupportedDragActions,
&Self::adjustSupportedDragActions>;
using AdjustSupportedDropActions = Override<QRangeModelImplBase::AdjustSupportedDropActions,
&Self::adjustSupportedDropActions>;
using MimeTypes = Override<QRangeModelImplBase::MimeTypes, &Self::mimeTypes>;
using CanDropMimeData = Override<QRangeModelImplBase::CanDropMimeData, &Self::canDropMimeData>;
using DropMimeData = Override<QRangeModelImplBase::DropMimeData, &Self::dropMimeData>;
using MimeData = Override<QRangeModelImplBase::MimeData, &Self::mimeData>;
protected:
~QRangeModelImpl()
{
deleteOwnedRows();
}
void deleteOwnedRows()
{
// We delete row objects if we are not operating on a reference or pointer
// to a range, as in that case, the owner of the referenced/pointed to
// range also owns the row entries.
// ### Problem: if we get a copy of a range (no matter if shared or not),
// then adding rows will create row objects in the model's copy, and the
// client can never delete those. But copied rows will be the same pointer,
// which we must not delete (as we didn't create them).
static constexpr bool modelCopied = !QRangeModelDetails::is_wrapped<Range>() &&
(std::is_reference_v<Range> || std::is_const_v<std::remove_reference_t<Range>>);
static constexpr bool modelShared = QRangeModelDetails::is_any_shared_ptr<Range>();
static constexpr bool default_row_deleter = protocol_traits::is_default &&
protocol_traits::has_deleteRow;
static constexpr bool ambiguousRowOwnership = (modelCopied || modelShared) &&
rows_are_raw_pointers && default_row_deleter;
static_assert(!ambiguousRowOwnership,
"Using of copied and shared tree and table models with rows as raw pointers, "
"and the default protocol is not allowed due to ambiguity of rows ownership. "
"Move the model in, use another row type, or implement a custom tree protocol.");
if constexpr (protocol_traits::has_deleteRow && !std::is_pointer_v<Range>
&& !QRangeModelDetails::is_any_of<Range, std::reference_wrapper>()) {
const auto begin = QRangeModelDetails::adl_begin(*m_data.model());
const auto end = QRangeModelDetails::adl_end(*m_data.model());
that().deleteRemovedRows(begin, end);
}
}
static constexpr bool canInsertRows()
{
if constexpr (dynamicColumns() && !row_features::has_resize) {
// If we operate on dynamic columns and cannot resize a newly
// constructed row, then we cannot insert.
return false;
} else if constexpr (!protocol_traits::has_newRow) {
// We also cannot insert if we cannot create a new row element
return false;
} else if constexpr (!range_features::has_insert_range
&& !std::is_copy_constructible_v<row_type>) {
// And if the row is a move-only type, then the range needs to be
// backed by a container that can move-insert default-constructed
// row elements.
return false;
} else {
return Structure::canInsertRowsImpl();
}
}
static constexpr bool canRemoveRows()
{
return Structure::canRemoveRowsImpl();
}
template <typename F>
bool writeAt(const QModelIndex &index, F&& writer)
{
row_reference row = rowData(index);
if (!QRangeModelDetails::isValid(row))
return false;
return row_traits::for_element_at(row, index.column(), [&writer](auto &&target) {
using target_type = decltype(target);
// we can only assign to an lvalue reference
if constexpr (std::is_lvalue_reference_v<target_type>
&& !std::is_const_v<std::remove_reference_t<target_type>>) {
return writer(std::forward<target_type>(target));
} else {
return false;
}
});
}
template <typename F>
bool readAt(const QModelIndex &index, F&& reader) const {
const_row_reference row = rowData(index);
if (!QRangeModelDetails::isValid(row))
return false;
return row_traits::for_element_at(row, index.column(), std::forward<F>(reader));
}
template <typename Value>
static QVariant read(const Value &value)
{
if constexpr (std::is_constructible_v<QVariant, Value>)
return QVariant(value);
else
return QVariant::fromValue(value);
}
template <typename Value>
static QVariant read(Value *value)
{
if (value) {
if constexpr (std::is_constructible_v<QVariant, Value *>)
return QVariant(value);
else
return read(*value);
}
return {};
}
template <typename Target>
static bool write(Target &target, const QVariant &value)
{
using Type = std::remove_reference_t<Target>;
if constexpr (std::is_constructible_v<Target, QVariant>) {
target = value;
return true;
} else if (value.canConvert<Type>()) {
target = value.value<Type>();
return true;
}
return false;
}
template <typename Target>
static bool write(Target *target, const QVariant &value)
{
if (target)
return write(*target, value);
return false;
}
template <typename ItemType>
QMetaProperty roleProperty(int role) const
{
struct {
operator QMetaProperty() const {
const QByteArray roleName = that.itemModel().roleNames().value(role);
const QMetaObject &mo = ItemType::staticMetaObject;
if (const int index = mo.indexOfProperty(roleName.data());
index >= 0) {
return mo.property(index);
}
return {};
}
const QRangeModelImpl &that;
const int role;
} findProperty{*this, role};
if constexpr (ModelData::cachesProperties)
return *m_data.properties.tryEmplace(role, findProperty).iterator;
else
return findProperty;
}
void connectPropertyOnRead(const QModelIndex &index, int role,
const QObject *gadget, const QMetaProperty &prop) const
{
if (!index.isValid())
return;
const typename ModelData::Connection connection = {gadget, role};
if (prop.hasNotifySignal() && this->autoConnectPolicy() == AutoConnectPolicy::OnRead
&& !m_data.connections.contains(connection)) {
if constexpr (isMutable())
Self::connectProperty(index, gadget, m_data.context, role, prop);
else
Self::connectPropertyConst(index, gadget, m_data.context, role, prop);
m_data.connections.insert(connection);
}
}
template <typename ItemType>
QVariant readRole(const QModelIndex &index, int role, ItemType *gadget) const
{
using item_type = std::remove_pointer_t<ItemType>;
QVariant result;
QMetaProperty prop = roleProperty<item_type>(role);
if (!prop.isValid() && role == Qt::EditRole) {
role = Qt::DisplayRole;
prop = roleProperty<item_type>(Qt::DisplayRole);
}
if (prop.isValid()) {
if constexpr (itemsAreQObjects)
connectPropertyOnRead(index, role, gadget, prop);
result = readProperty(prop, gadget);
}
return result;
}
template <typename ItemType>
QVariant readRole(const QModelIndex &index, int role, const ItemType &gadget) const
{
return readRole(index, role, &gadget);
}
template <typename ItemType>
static QVariant readProperty(const QMetaProperty &prop, ItemType *gadget)
{
if constexpr (std::is_base_of_v<QObject, ItemType>)
return prop.read(gadget);
else
return prop.readOnGadget(gadget);
}
template <typename ItemType>
QVariant readProperty(const QModelIndex &index, ItemType *gadget) const
{
using item_type = std::remove_pointer_t<ItemType>;
const QMetaObject &mo = item_type::staticMetaObject;
const QMetaProperty prop = mo.property(index.column() + mo.propertyOffset());
if constexpr (rowsAreQObjects)
connectPropertyOnRead(index, Qt::DisplayRole, gadget, prop);
return readProperty(prop, gadget);
}
template <typename ItemType>
QVariant readProperty(const QModelIndex &index, const ItemType &gadget) const
{
return readProperty(index, &gadget);
}
template <typename ItemType>
bool writeRole(int role, ItemType *gadget, const QVariant &data)
{
using item_type = std::remove_pointer_t<ItemType>;
auto prop = roleProperty<item_type>(role);
if (!prop.isValid() && role == Qt::EditRole)
prop = roleProperty<item_type>(Qt::DisplayRole);
return prop.isValid() ? writeProperty(prop, gadget, data) : false;
}
template <typename ItemType>
bool writeRole(int role, ItemType &&gadget, const QVariant &data)
{
return writeRole(role, &gadget, data);
}
template <typename ItemType>
static bool writeProperty(const QMetaProperty &prop, ItemType *gadget, const QVariant &data)
{
if constexpr (std::is_base_of_v<QObject, ItemType>)
return prop.write(gadget, data);
else
return prop.writeOnGadget(gadget, data);
}
template <typename ItemType>
static bool writeProperty(int property, ItemType *gadget, const QVariant &data)
{
using item_type = std::remove_pointer_t<ItemType>;
const QMetaObject &mo = item_type::staticMetaObject;
return writeProperty(mo.property(property + mo.propertyOffset()), gadget, data);
}
template <typename ItemType>
static bool writeProperty(int property, ItemType &&gadget, const QVariant &data)
{
return writeProperty(property, &gadget, data);
}
template <typename ItemType>
static bool resetProperty(int property, ItemType *object)
{
using item_type = std::remove_pointer_t<ItemType>;
const QMetaObject &mo = item_type::staticMetaObject;
bool success = true;
if (property == -1) {
// reset all properties
if constexpr (std::is_base_of_v<QObject, item_type>) {
for (int p = mo.propertyOffset(); p < mo.propertyCount(); ++p)
success = writeProperty(mo.property(p), object, {}) && success;
} else { // reset a gadget by assigning a default-constructed
*object = {};
}
} else {
success = writeProperty(mo.property(property + mo.propertyOffset()), object, {});
}
return success;
}
template <typename ItemType>
static bool resetProperty(int property, ItemType &&object)
{
return resetProperty(property, &object);
}
// helpers
const_row_reference rowData(const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
return that().rowDataImpl(index);
}
row_reference rowData(const QModelIndex &index)
{
Q_ASSERT(index.isValid());
return that().rowDataImpl(index);
}
const range_type *childRange(const QModelIndex &index) const
{
if (!index.isValid())
return m_data.model();
if (index.column()) // only items at column 0 can have children
return nullptr;
return that().childRangeImpl(index);
}
range_type *childRange(const QModelIndex &index)
{
if (!index.isValid())
return m_data.model();
if (index.column()) // only items at column 0 can have children
return nullptr;
return that().childRangeImpl(index);
}
template <typename, typename, typename> friend class QRangeModelAdapter;
ModelData m_data;
};
// Implementations that depends on the model structure (flat vs tree) that will
// be specialized based on a protocol type. The main template implements tree
// support through a protocol type.
template <typename Range, typename Protocol>
class QGenericTreeItemModelImpl
: public QRangeModelImpl<QGenericTreeItemModelImpl<Range, Protocol>, Range, Protocol>
{
using Base = QRangeModelImpl<QGenericTreeItemModelImpl<Range, Protocol>, Range, Protocol>;
friend class QRangeModelImpl<QGenericTreeItemModelImpl<Range, Protocol>, Range, Protocol>;
using range_type = typename Base::range_type;
using range_features = typename Base::range_features;
using row_type = typename Base::row_type;
using row_ptr = typename Base::row_ptr;
using const_row_ptr = typename Base::const_row_ptr;
using tree_traits = typename Base::protocol_traits;
static constexpr bool is_mutable_impl = tree_traits::has_mutable_childRows;
static constexpr bool rows_are_any_refs_or_pointers = Base::rows_are_raw_pointers ||
QRangeModelDetails::is_smart_ptr<row_type>() ||
QRangeModelDetails::is_any_of<row_type, std::reference_wrapper>();
static_assert(!Base::dynamicColumns(), "A tree must have a static number of columns!");
public:
QGenericTreeItemModelImpl(Range &&model, Protocol &&p, QRangeModel *itemModel)
: 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::adl_begin(range), QRangeModelDetails::adl_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
{
if (!parent.isValid())
return this->createIndex(row, column);
// only items at column 0 can have children
if (parent.column())
return QModelIndex();
const_row_ptr grandParent = static_cast<const_row_ptr>(parent.constInternalPointer());
const auto &parentSiblings = childrenOf(grandParent);
const auto it = QRangeModelDetails::pos(parentSiblings, parent.row());
return this->createIndex(row, column, QRangeModelDetails::pointerTo(*it));
}
QModelIndex parentImpl(const QModelIndex &child) const
{
if (!child.isValid())
return {};
// no pointer to parent row - no parent
const_row_ptr parentRow = static_cast<const_row_ptr>(child.constInternalPointer());
if (!parentRow)
return {};
// get the siblings of the parent via the grand parent
auto &&grandParent = this->protocol().parentRow(QRangeModelDetails::refTo(parentRow));
const range_type &parentSiblings = childrenOf(QRangeModelDetails::pointerTo(grandParent));
// find the index of parentRow
const auto begin = QRangeModelDetails::adl_begin(parentSiblings);
const auto end = QRangeModelDetails::adl_end(parentSiblings);
const auto it = std::find_if(begin, end, [parentRow](auto &&s){
return QRangeModelDetails::pointerTo(std::forward<decltype(s)>(s)) == parentRow;
});
if (it != end)
return this->createIndex(std::distance(begin, it), 0,
QRangeModelDetails::pointerTo(grandParent));
return {};
}
int rowCountImpl(const QModelIndex &parent) const
{
return Base::size(this->childRange(parent));
}
int columnCountImpl(const QModelIndex &) const
{
// All levels of a tree have to have the same, fixed, column count.
// If static_column_count is -1 for a tree, static assert fires
return Base::fixedColumnCount();
}
static constexpr Qt::ItemFlags defaultFlags()
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
static constexpr bool canInsertRowsImpl()
{
// We must not insert rows if we cannot adjust the parents of the
// children of the following rows. We don't have to do that if the
// range operates on pointers.
return (rows_are_any_refs_or_pointers || tree_traits::has_setParentRow)
&& Base::dynamicRows() && range_features::has_insert;
}
static constexpr bool canRemoveRowsImpl()
{
// We must not remove rows if we cannot adjust the parents of the
// children of the following rows. We don't have to do that if the
// range operates on pointers.
return (rows_are_any_refs_or_pointers || tree_traits::has_setParentRow)
&& Base::dynamicRows() && range_features::has_erase;
}
static constexpr bool canMoveColumns(const QModelIndex &, const QModelIndex &)
{
return true;
}
static constexpr bool canMoveRows(const QModelIndex &, const QModelIndex &)
{
return true;
}
bool moveRowsAcross(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destParent, int destRow)
{
// If rows are pointers, then reference to the parent row don't
// change, so we can move them around freely. Otherwise we need to
// be able to explicitly update the parent pointer.
if constexpr (!rows_are_any_refs_or_pointers && !tree_traits::has_setParentRow) {
return false;
} else if constexpr (!(range_features::has_insert && range_features::has_erase)) {
return false;
} else if (!this->beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
destParent, destRow)) {
return false;
}
range_type *source = this->childRange(sourceParent);
range_type *destination = this->childRange(destParent);
// If we can insert data from another range into, then
// use that to move the old data over.
const auto destStart = QRangeModelDetails::pos(destination, destRow);
if constexpr (range_features::has_insert_range) {
const auto sourceStart = QRangeModelDetails::pos(*source, sourceRow);
const auto sourceEnd = std::next(sourceStart, count);
destination->insert(destStart, std::move_iterator(sourceStart),
std::move_iterator(sourceEnd));
} else if constexpr (std::is_copy_constructible_v<row_type>) {
// otherwise we have to make space first, and copy later.
destination->insert(destStart, count, row_type{});
}
row_ptr parentRow = destParent.isValid()
? QRangeModelDetails::pointerTo(this->rowData(destParent))
: nullptr;
// if the source's parent was already inside the new parent row,
// then the source row might have become invalid, so reset it.
if (parentRow == static_cast<row_ptr>(sourceParent.internalPointer())) {
if (sourceParent.row() < destRow) {
source = this->childRange(sourceParent);
} else {
// the source parent moved down within destination
source = this->childRange(this->createIndex(sourceParent.row() + count, 0,
sourceParent.internalPointer()));
}
}
// move the data over and update the parent pointer
{
const auto writeStart = QRangeModelDetails::pos(destination, destRow);
const auto writeEnd = std::next(writeStart, count);
const auto sourceStart = QRangeModelDetails::pos(source, sourceRow);
const auto sourceEnd = std::next(sourceStart, count);
for (auto write = writeStart, read = sourceStart; write != writeEnd; ++write, ++read) {
// move data over if not already done, otherwise
// only fix the parent pointer
if constexpr (!range_features::has_insert_range)
*write = std::move(*read);
this->protocol().setParentRow(QRangeModelDetails::refTo(*write), parentRow);
}
// remove the old rows from the source parent
source->erase(sourceStart, sourceEnd);
}
// Fix the parent pointers in children of both source and destination
// ranges, as the references to the entries might have become invalid.
// We don't have to do that if the rows are pointers, as in that case
// the references to the entries are stable.
resetParentInChildren(destination);
resetParentInChildren(source);
this->endMoveRows();
return true;
}
auto makeEmptyRow(row_ptr parentRow)
{
// tree traversal protocol: if we are here, then it must be possible
// to change the parent of a row.
static_assert(tree_traits::has_setParentRow);
row_type empty_row = this->protocol().newRow();
if (QRangeModelDetails::isValid(empty_row) && parentRow)
this->protocol().setParentRow(QRangeModelDetails::refTo(empty_row), parentRow);
return empty_row;
}
template <typename It, typename Sentinel>
void deleteRemovedRows(It &&begin, Sentinel &&end)
{
if constexpr (tree_traits::has_deleteRow) {
for (auto it = begin; it != end; ++it) {
if constexpr (Base::isMutable()) {
decltype(auto) children = this->protocol().childRows(QRangeModelDetails::refTo(*it));
if (QRangeModelDetails::isValid(children)) {
deleteRemovedRows(QRangeModelDetails::adl_begin(children),
QRangeModelDetails::adl_end(children));
QRangeModelDetails::refTo(children) = range_type{ };
}
}
this->protocol().deleteRow(std::move(*it));
}
}
}
void resetParentInChildren(range_type *children)
{
const auto persistentIndexList = this->persistentIndexList();
const auto [firstColumn, lastColumn] = [&persistentIndexList]{
int first = std::numeric_limits<int>::max();
int last = -1;
for (const auto &pmi : persistentIndexList) {
first = (std::min)(pmi.column(), first);
last = (std::max)(pmi.column(), last);
}
return std::pair(first, last);
}();
resetParentInChildrenRecursive(children, firstColumn, lastColumn);
}
void resetParentInChildrenRecursive(range_type *children, int pmiFromColumn, int pmiToColumn)
{
if constexpr (tree_traits::has_setParentRow && !rows_are_any_refs_or_pointers) {
const bool changePersistentIndexes = pmiToColumn >= pmiFromColumn;
const auto begin = QRangeModelDetails::adl_begin(*children);
const auto end = QRangeModelDetails::adl_end(*children);
for (auto it = begin; it != end; ++it) {
decltype(auto) maybeChildren = this->protocol().childRows(*it);
if (QRangeModelDetails::isValid(maybeChildren)) {
auto &childrenRef = QRangeModelDetails::refTo(maybeChildren);
auto *parentRow = QRangeModelDetails::pointerTo(*it);
int row = 0;
for (auto &child : childrenRef) {
const_row_ptr oldParent = this->protocol().parentRow(child);
if (oldParent != parentRow) {
if (changePersistentIndexes) {
for (int column = pmiFromColumn; column <= pmiToColumn; ++column) {
this->changePersistentIndex(this->createIndex(row, column, oldParent),
this->createIndex(row, column, parentRow));
}
}
this->protocol().setParentRow(child, parentRow);
}
++row;
}
resetParentInChildrenRecursive(&childrenRef, pmiFromColumn, pmiToColumn);
}
}
}
}
bool autoConnectPropertiesRange(const range_type &range, const QModelIndex &parent) const
{
int rowIndex = 0;
for (const auto &row : range) {
if (!this->autoConnectPropertiesInRow(row, rowIndex, parent))
return false;
Q_ASSERT(QRangeModelDetails::isValid(row));
const auto &children = this->protocol().childRows(QRangeModelDetails::refTo(row));
if (QRangeModelDetails::isValid(children)) {
if (!autoConnectPropertiesRange(QRangeModelDetails::refTo(children),
this->itemModel().index(rowIndex, 0, parent))) {
return false;
}
}
++rowIndex;
}
return true;
}
bool autoConnectPropertiesImpl() const
{
return autoConnectPropertiesRange(*this->m_data.model(), {});
}
decltype(auto) rowDataImpl(const QModelIndex &index) const
{
const_row_ptr parentRow = static_cast<const_row_ptr>(index.constInternalPointer());
const range_type &siblings = childrenOf(parentRow);
Q_ASSERT(index.row() < int(Base::size(siblings)));
return *QRangeModelDetails::pos(siblings, index.row());
}
decltype(auto) rowDataImpl(const QModelIndex &index)
{
row_ptr parentRow = static_cast<row_ptr>(index.internalPointer());
range_type &siblings = childrenOf(parentRow);
Q_ASSERT(index.row() < int(Base::size(siblings)));
return *QRangeModelDetails::pos(siblings, index.row());
}
const range_type *childRangeImpl(const QModelIndex &index) const
{
const auto &row = this->rowData(index);
if (!QRangeModelDetails::isValid(row))
return static_cast<const range_type *>(nullptr);
decltype(auto) children = this->protocol().childRows(QRangeModelDetails::refTo(row));
return QRangeModelDetails::pointerTo(std::forward<decltype(children)>(children));
}
range_type *childRangeImpl(const QModelIndex &index)
{
auto &row = this->rowData(index);
if (!QRangeModelDetails::isValid(row))
return static_cast<range_type *>(nullptr);
decltype(auto) children = this->protocol().childRows(QRangeModelDetails::refTo(row));
using Children = std::remove_reference_t<decltype(children)>;
if constexpr (QRangeModelDetails::is_any_of<Children, std::optional>())
if constexpr (std::is_default_constructible<typename Children::value_type>()) {
if (!children)
children.emplace(range_type{});
}
return QRangeModelDetails::pointerTo(std::forward<decltype(children)>(children));
}
const range_type &childrenOf(const_row_ptr row) const
{
return row ? QRangeModelDetails::refTo(this->protocol().childRows(*row))
: *this->m_data.model();
}
range_type &childrenOf(row_ptr row)
{
return row ? QRangeModelDetails::refTo(this->protocol().childRows(*row))
: *this->m_data.model();
}
template <typename LessThan>
void sortImplRecursive(range_type &range, row_ptr parentRow, const LessThan &lessThan)
{
for (auto &row : range) {
decltype(auto) children = this->protocol().childRows(QRangeModelDetails::refTo(row));
if (QRangeModelDetails::isValid(children)) {
sortImplRecursive(QRangeModelDetails::refTo(children),
QRangeModelDetails::pointerTo(row), lessThan);
}
}
this->sortSubRange(range, parentRow, lessThan);
}
template <typename LessThan>
void sortImpl(const LessThan &lessThan)
{
sortImplRecursive(*this->m_data.model(), nullptr, lessThan);
resetParentInChildren(this->m_data.model());
}
void prunePersistentIndexList(QModelIndexList &list, row_ptr expectedParent)
{
erase_if(list, [expectedParent](const QModelIndex &index){
return static_cast<row_ptr>(index.internalPointer()) != expectedParent;
});
}
void matchImplRecursive(const range_type &range, const_row_ptr parentPtr, int from, int to,
int role, const QVariant &value, int hits, Qt::MatchFlags flags, int column,
QModelIndexList &result, const QCollator &collator) const
{
auto it = QRangeModelDetails::pos(range, from);
auto end = QRangeModelDetails::adl_end(range);
const bool recurse = flags.testAnyFlag(Qt::MatchRecursive);
const bool allHits = (hits == -1);
for (int r = from; it != end && r < to && (allHits || result.size() < hits); ++it, ++r) {
const QModelIndex index = this->createIndex(r, column, parentPtr);
if (this->matchRow(*it, index, role, value, flags, collator))
result.append(index);
if (recurse) {
decltype(auto) children = this->protocol().childRows(QRangeModelDetails::refTo(*it));
if (QRangeModelDetails::isValid(children)) {
matchImplRecursive(QRangeModelDetails::refTo(children),
QRangeModelDetails::pointerTo(*it), 0,
int(QRangeModelDetails::size(QRangeModelDetails::refTo(children))),
role, value, hits, flags, column, result, collator);
}
}
}
}
QModelIndexList matchImpl(const QModelIndex &start, int role, const QVariant &value, int hits,
Qt::MatchFlags flags, const QCollator &collator) const
{
QModelIndexList result;
const bool wrap = flags.testAnyFlag(Qt::MatchWrap);
const int column = start.column();
const int from = start.row();
const int to = this->rowCount(start.parent());
for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) {
const int fromRow = (i == 0) ? from : 0;
const int toRow = (i == 0) ? to : from;
matchImplRecursive(*this->m_data.model(), nullptr, fromRow, toRow,
role, value, hits, flags, column, result, collator);
}
return result;
}
// tree models don't overwrite the data at index, but instead insert a
// child item
bool dropOnItem(const QMimeData *, const QModelIndex &)
{
return false;
}
};
// specialization for flat models without protocol
template <typename Range>
class QGenericTableItemModelImpl
: public QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>
{
using Base = QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
friend class QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
static constexpr bool is_mutable_impl = true;
public:
using range_type = typename Base::range_type;
using range_features = typename Base::range_features;
using row_type = typename Base::row_type;
using row_ptr = typename Base::row_ptr;
using const_row_ptr = typename Base::const_row_ptr;
using row_traits = typename Base::row_traits;
using row_features = typename Base::row_features;
explicit QGenericTableItemModelImpl(Range &&model, QRangeModel *itemModel)
: Base(std::forward<Range>(model), {}, itemModel)
{}
protected:
QModelIndex indexImpl(int row, int column, const QModelIndex &) const
{
if constexpr (Base::dynamicColumns()) {
if (column < int(Base::size(*QRangeModelDetails::pos(*this->m_data.model(), row))))
return this->createIndex(row, column);
#ifndef QT_NO_DEBUG
// if we got here, then column < columnCount(), but this row is too short
qCritical("QRangeModel: Column-range at row %d is not large enough!", row);
#endif
return {};
} else {
return this->createIndex(row, column);
}
}
QModelIndex parentImpl(const QModelIndex &) const
{
return {};
}
int rowCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return int(Base::size(*this->m_data.model()));
}
int columnCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
// in a table, all rows have the same number of columns (as the first row)
if constexpr (Base::dynamicColumns()) {
return int(Base::size(*this->m_data.model()) == 0
? 0
: Base::size(*QRangeModelDetails::adl_begin(*this->m_data.model())));
} else {
return Base::fixedColumnCount();
}
}
static constexpr Qt::ItemFlags defaultFlags()
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
}
static constexpr bool canInsertRowsImpl()
{
return Base::dynamicRows() && range_features::has_insert;
}
static constexpr bool canRemoveRowsImpl()
{
return Base::dynamicRows() && range_features::has_erase;
}
static constexpr bool canMoveColumns(const QModelIndex &source, const QModelIndex &destination)
{
return !source.isValid() && !destination.isValid();
}
static constexpr bool canMoveRows(const QModelIndex &source, const QModelIndex &destination)
{
return !source.isValid() && !destination.isValid();
}
constexpr bool moveRowsAcross(const QModelIndex &, int , int,
const QModelIndex &, int) noexcept
{
// table/flat model: can't move rows between different parents
return false;
}
auto makeEmptyRow(typename Base::row_ptr)
{
row_type empty_row = this->protocol().newRow();
// dynamically sized rows all have to have the same column count
if constexpr (Base::dynamicColumns() && row_features::has_resize) {
if (QRangeModelDetails::isValid(empty_row))
QRangeModelDetails::refTo(empty_row).resize(this->columnCount({}));
}
return empty_row;
}
template <typename It, typename Sentinel>
void deleteRemovedRows(It &&begin, Sentinel &&end)
{
if constexpr (Base::protocol_traits::has_deleteRow) {
for (auto it = begin; it != end; ++it)
this->protocol().deleteRow(std::move(*it));
}
}
decltype(auto) rowDataImpl(const QModelIndex &index) const
{
Q_ASSERT(q20::cmp_less(index.row(), Base::size(*this->m_data.model())));
return *QRangeModelDetails::pos(*this->m_data.model(), index.row());
}
decltype(auto) rowDataImpl(const QModelIndex &index)
{
Q_ASSERT(q20::cmp_less(index.row(), Base::size(*this->m_data.model())));
return *QRangeModelDetails::pos(*this->m_data.model(), index.row());
}
const range_type *childRangeImpl(const QModelIndex &) const
{
return nullptr;
}
range_type *childRangeImpl(const QModelIndex &)
{
return nullptr;
}
const range_type &childrenOf(const_row_ptr row) const
{
Q_ASSERT(!row);
return *this->m_data.model();
}
range_type &childrenOf(row_ptr row)
{
Q_ASSERT(!row);
return *this->m_data.model();
}
void resetParentInChildren(range_type *)
{
}
bool autoConnectPropertiesImpl() const
{
bool result = true;
int rowIndex = 0;
for (const auto &row : *this->m_data.model()) {
result &= this->autoConnectPropertiesInRow(row, rowIndex, {});
++rowIndex;
}
return result;
}
template <typename LessThan>
void sortImpl(const LessThan &lessThan)
{
this->sortSubRange(*this->m_data.model(), nullptr, lessThan);
}
void prunePersistentIndexList(QModelIndexList &, typename Base::row_ptr) {}
QModelIndexList matchImpl(const QModelIndex &start, int role, const QVariant &value,
int hits, Qt::MatchFlags flags, const QCollator &collator) const
{
QModelIndexList result;
const bool wrap = flags.testAnyFlag(Qt::MatchWrap);
const bool allHits = (hits == -1);
const int column = start.column();
int from = start.row();
int to = this->rowCount({});
decltype(auto) siblings = *this->m_data.model();
for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) {
auto it = QRangeModelDetails::pos(siblings, from);
auto end = QRangeModelDetails::adl_end(siblings);
for (int r = from; it != end && r < to && (allHits || result.size() < hits);
++it, ++r) {
if (!QRangeModelDetails::isValid(*it))
continue;
const QModelIndex index = this->createIndex(r, column, nullptr);
if (this->matchRow(*it, index, role, value, flags, collator))
result.append(index);
}
from = 0;
to = start.row();
}
return result;
}
// flat models can overwrite data of the dropped-on item
bool dropOnItem(const QMimeData *data, const QModelIndex &index)
{
return this->dropDataOnItem(data, index);
}
};
QT_END_NAMESPACE
#endif // Q_QDOC
#endif // QRANGEMODEL_IMPL_H