// 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 #include #include #include QT_BEGIN_NAMESPACE /*! \qmltype FunctionSorter \inherits Sorter \inqmlmodule QtQml.Models \since 6.10 \preliminary \brief Sorts data in a \l SortFilterProxyModel based on the evaluation of the designated 'sort' method. FunctionSorter allows user to define the designated 'sort' method and it will be evaluated to sort the data. The method takes two arguments (lhs and rhs) of the specified parameter type and the data can be accessed as below for evaluation, \qml SortFilterProxyModel { model: sourceModel sorters: [ FunctionSorter { id: functionSorter component RoleData: QtObject { property real age } function sort(lhsData: RoleData, rhsData: RoleData) : int { return (lhsData.age < rhsData.age) ? -1 : ((lhsData === rhsData.age) ? 0 : 1) } } ] } \endqml \note The user needs to explicitly invoke \l{SortFilterProxyModel::invalidateSorter} whenever any external qml property used within the designated 'sort' method changes. This behaviour is subject to change in the future, like implicit invalidation and thus the user doesn't need to explicitly invoke \l{SortFilterProxyModel::invalidateSorter}. */ QQmlFunctionSorter::QQmlFunctionSorter(QObject *parent) : QQmlSorterBase (new QQmlFunctionSorterPrivate, parent) { } QQmlFunctionSorter::~QQmlFunctionSorter() { Q_D(QQmlFunctionSorter); if (d->m_lhsParameterData.metaType().flags() & QMetaType::PointerToQObject) delete d->m_lhsParameterData.value(); if (d->m_rhsParameterData.metaType().flags() & QMetaType::PointerToQObject) delete d->m_rhsParameterData.value(); } void QQmlFunctionSorter::componentComplete() { Q_D(QQmlFunctionSorter); const auto *metaObj = this->metaObject(); for (int idx = metaObj->methodOffset(); idx < metaObj->methodCount(); idx++) { // Once we find the method signature, break the loop QMetaMethod method = metaObj->method(idx); if (method.nameView() == "sort") { d->m_method = method; break; } } if (!d->m_method.isValid()) return; if (d->m_method.parameterCount() != 2) { qWarning("sort method requires two parameters"); return; } QQmlData *data = QQmlData::get(this); if (!data || !data->outerContext) { qWarning("sort requires a QML context"); return; } const QMetaType parameterType = d->m_method.parameterMetaType(0); if (parameterType != d->m_method.parameterMetaType(1)) { qWarning("sort parameters have to be equal"); return; } auto cu = QQmlMetaType::obtainCompilationUnit(parameterType); const QQmlType parameterQmlType = QQmlMetaType::qmlType(parameterType); QQmlRefPointer context = data->outerContext; QQmlEngine *engine = context->engine(); // The code below creates an instance of the inline component, composite, // or specific C++ QObject types. The created instance, along with the // data, are passed as an arguments to the 'sort' method, which is invoked // during the call to QQmlFunctionSorter::compare. // To create an instance of required component types (be it inline or // composite), an executable compilation unit is required, and this can be // obtained by looking up via metatype in the type registry // (QQmlMetaType::obtainCompilationUnit). Pass it through the QML engine to // make it executable. Further, use the executable compilation unit to run // an object creator and produce an instance. if (parameterType.flags() & QMetaType::PointerToQObject) { QObject *created0 = nullptr; QObject *created1 = nullptr; if (parameterQmlType.isInlineComponentType()) { const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu)); const QString icName = parameterQmlType.elementName(); created0 = QQmlObjectCreator(context, executableCu, context, icName).create( executableCu->inlineComponentId(icName), nullptr, nullptr, QQmlObjectCreator::InlineComponent); created1 = QQmlObjectCreator(context, executableCu, context, icName).create( executableCu->inlineComponentId(icName), nullptr, nullptr, QQmlObjectCreator::InlineComponent); } else if (parameterQmlType.isComposite()) { const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu)); created0 = QQmlObjectCreator(context, executableCu, context, QString()).create(); created1 = QQmlObjectCreator(context, executableCu, context, QString()).create(); } else { created0 = parameterQmlType.metaObject()->newInstance(); created1 = parameterQmlType.metaObject()->newInstance(); } const auto names = d->m_method.parameterNames(); created0->setObjectName(names[0]); created1->setObjectName(names[1]); d->m_lhsParameterData = QVariant::fromValue(created0); d->m_rhsParameterData = QVariant::fromValue(created1); } else { d->m_lhsParameterData = QVariant(parameterType); d->m_rhsParameterData = QVariant(parameterType); } } /*! \internal */ QPartialOrdering QQmlFunctionSorter::compare( const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel *proxyModel) const { Q_D(const QQmlFunctionSorter); if (!d->m_method.isValid() || !d->m_lhsParameterData.isValid() || !d->m_rhsParameterData.isValid()) { return QPartialOrdering::Unordered; } int retVal = 0; QSortFilterProxyModelHelper::setProperties(&d->m_lhsParameterData, proxyModel, sourceLeft); QSortFilterProxyModelHelper::setProperties(&d->m_rhsParameterData, proxyModel, sourceRight); void *argv[] = {&retVal, d->m_lhsParameterData.data(), d->m_rhsParameterData.data()}; QMetaObject::metacall( const_cast(this), QMetaObject::InvokeMetaMethod, d->m_method.methodIndex(), argv); return (retVal == 0) ? QPartialOrdering::Equivalent : ((retVal < 0) ? QPartialOrdering::Less : QPartialOrdering::Greater); } QT_END_NAMESPACE #include "moc_qqmlfunctionsorter_p.cpp"