// Copyright (C) 2016 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 #include "qqmllistaccessor_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE QQmlListAccessor::QQmlListAccessor() : m_type(Invalid) { } QQmlListAccessor::~QQmlListAccessor() { } QVariant QQmlListAccessor::list() const { return d; } void QQmlListAccessor::setList(const QVariant &v) { d = v; // An incoming JS array as model is treated as a variant list, so we need to // convert it first with toVariant(). QMetaType variantsType = d.metaType(); if (variantsType == QMetaType::fromType()) { d = d.value().toVariant(); variantsType = d.metaType(); } if (!d.isValid()) { m_type = Invalid; return; } if (variantsType == QMetaType::fromType()) { m_type = StringList; return; } if (variantsType == QMetaType::fromType>()) { m_type = UrlList; return; } if (variantsType == QMetaType::fromType()) { m_type = VariantList; return; } if (variantsType == QMetaType::fromType>()) { m_type = ObjectList; return; } if (variantsType.flags() & QMetaType::IsQmlList) { d = QVariant::fromValue(QQmlListReference(d)); m_type = ListProperty; return; } if (variantsType == QMetaType::fromType()) { m_type = ListProperty; return; } if (variantsType.flags() & QMetaType::PointerToQObject) { m_type = Instance; return; } if (int i = 0; [&](){bool ok = false; i = v.toInt(&ok); return ok;}()) { // Here we have to check for an upper limit, because down the line code might (well, will) // allocate memory depending on the number of elements. The upper limit cannot be INT_MAX: // QVector> something; // something.resize(count()); // (See e.g. QQuickRepeater::regenerate()) // This will allocate data along the lines of: // sizeof(QPointer) * count() + QVector::headerSize // So, doing an approximate round-down-to-nice-number, we get: const int upperLimit = 100 * 1000 * 1000; if (i < 0) { qWarning("Model size of %d is less than 0", i); m_type = Invalid; return; } if (i > upperLimit) { qWarning("Model size of %d is bigger than the upper limit %d", i, upperLimit); m_type = Invalid; return; } m_type = Integer; d = i; return; } const QQmlType type = QQmlMetaType::qmlListType(variantsType); if (type.isSequentialContainer()) { m_metaSequence = type.listMetaSequence(); m_type = Sequence; return; } QSequentialIterable iterable; if (QMetaType::convert( variantsType, d.constData(), QMetaType::fromType(), &iterable)) { const QMetaSequence sequence = iterable.metaContainer(); if (sequence.hasSize() && sequence.canGetValueAtIndex()) { // If the resulting iterable is useful for anything, use it. m_metaSequence = sequence; m_type = Sequence; return; } if (sequence.hasConstIterator() && sequence.canGetValueAtConstIterator()) { // As a last resort, try to read the contents of the container via an iterator // and build a QVariantList from them. QVariantList variantList; for (auto it = iterable.constBegin(), end = iterable.constEnd(); it != end; ++it) variantList.push_back(*it); d = std::move(variantList); m_type = VariantList; return; } } m_type = Instance; return; } qsizetype QQmlListAccessor::count() const { switch(m_type) { case StringList: Q_ASSERT(d.metaType() == QMetaType::fromType()); return reinterpret_cast(d.constData())->size(); case UrlList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); return reinterpret_cast *>(d.constData())->size(); case VariantList: Q_ASSERT(d.metaType() == QMetaType::fromType()); return reinterpret_cast(d.constData())->size(); case ObjectList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); return reinterpret_cast *>(d.constData())->size(); case ListProperty: Q_ASSERT(d.metaType() == QMetaType::fromType()); return reinterpret_cast(d.constData())->count(); case Sequence: Q_ASSERT(m_metaSequence != QMetaSequence()); return m_metaSequence.size(d.constData()); case Instance: return 1; case Integer: return *reinterpret_cast(d.constData()); case Invalid: return 0; } Q_UNREACHABLE_RETURN(0); } QVariant QQmlListAccessor::at(qsizetype idx) const { Q_ASSERT(idx >= 0 && idx < count()); switch(m_type) { case StringList: Q_ASSERT(d.metaType() == QMetaType::fromType()); return QVariant::fromValue(reinterpret_cast(d.constData())->at(idx)); case UrlList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); return QVariant::fromValue(reinterpret_cast *>(d.constData())->at(idx)); case VariantList: Q_ASSERT(d.metaType() == QMetaType::fromType()); return reinterpret_cast(d.constData())->at(idx); case ObjectList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); return QVariant::fromValue(reinterpret_cast *>(d.constData())->at(idx)); case ListProperty: Q_ASSERT(d.metaType() == QMetaType::fromType()); return QVariant::fromValue(reinterpret_cast(d.constData())->at(idx)); case Sequence: { Q_ASSERT(m_metaSequence != QMetaSequence()); QVariant result; const QMetaType valueMetaType = m_metaSequence.valueMetaType(); if (valueMetaType == QMetaType::fromType()) { m_metaSequence.valueAtIndex(d.constData(), idx, &result); } else { result = QVariant(valueMetaType); m_metaSequence.valueAtIndex(d.constData(), idx, result.data()); } return result; } case Instance: return d; case Integer: return QVariant(idx); case Invalid: return QVariant(); } Q_UNREACHABLE_RETURN(QVariant()); } void QQmlListAccessor::set(qsizetype idx, const QVariant &value) { Q_ASSERT(idx >= 0 && idx < count()); switch (m_type) { case StringList: Q_ASSERT(d.metaType() == QMetaType::fromType()); (*static_cast(d.data()))[idx] = value.toString(); break; case UrlList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); (*static_cast *>(d.data()))[idx] = value.value(); break; case VariantList: Q_ASSERT(d.metaType() == QMetaType::fromType()); (*static_cast(d.data()))[idx] = value; break; case ObjectList: Q_ASSERT(d.metaType() == QMetaType::fromType>()); (*static_cast *>(d.data()))[idx] = value.value(); break; case ListProperty: Q_ASSERT(d.metaType() == QMetaType::fromType()); static_cast(d.data())->replace(idx, value.value()); break; case Sequence: { Q_ASSERT(m_metaSequence != QMetaSequence()); const QMetaType valueMetaType = m_metaSequence.valueMetaType(); if (valueMetaType == QMetaType::fromType()) { m_metaSequence.setValueAtIndex(d.data(), idx, &value); } else if (valueMetaType == value.metaType()) { m_metaSequence.setValueAtIndex(d.data(), idx, value.constData()); } else { QVariant converted = value; converted.convert(valueMetaType); m_metaSequence.setValueAtIndex(d.data(), idx, converted.constData()); } break; } case Instance: d = value; break; case Integer: break;; case Invalid: break; } } bool QQmlListAccessor::isValid() const { return m_type != Invalid; } QT_END_NAMESPACE