| 1 | // Copyright (C) 2025 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <QtQmlModels/private/qqmlfunctionsorter_p.h> |
| 5 | #include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h> |
| 6 | #include <QtQml/private/qqmlobjectcreator_p.h> |
| 7 | |
| 8 | QT_BEGIN_NAMESPACE |
| 9 | |
| 10 | /*! |
| 11 | \qmltype FunctionSorter |
| 12 | \inherits Sorter |
| 13 | \inqmlmodule QtQml.Models |
| 14 | \since 6.10 |
| 15 | \preliminary |
| 16 | \brief Sorts data in a \l SortFilterProxyModel based on the evaluation of |
| 17 | the designated 'sort' method. |
| 18 | |
| 19 | FunctionSorter allows user to define the designated 'sort' method and it |
| 20 | will be evaluated to sort the data. The method takes two arguments |
| 21 | (lhs and rhs) of the specified parameter type and the data can |
| 22 | be accessed as below for evaluation, |
| 23 | |
| 24 | \qml |
| 25 | SortFilterProxyModel { |
| 26 | model: sourceModel |
| 27 | sorters: [ |
| 28 | FunctionSorter { |
| 29 | id: functionSorter |
| 30 | component RoleData: QtObject { |
| 31 | property real age |
| 32 | } |
| 33 | function sort(lhsData: RoleData, rhsData: RoleData) : int { |
| 34 | return (lhsData.age < rhsData.age) ? -1 : ((lhsData === rhsData.age) ? 0 : 1) |
| 35 | } |
| 36 | } |
| 37 | ] |
| 38 | } |
| 39 | \endqml |
| 40 | |
| 41 | \note The user needs to explicitly invoke |
| 42 | \l{SortFilterProxyModel::invalidateSorter} whenever any external qml |
| 43 | property used within the designated 'sort' method changes. This behaviour |
| 44 | is subject to change in the future, like implicit invalidation and thus the |
| 45 | user doesn't need to explicitly invoke |
| 46 | \l{SortFilterProxyModel::invalidateSorter}. |
| 47 | */ |
| 48 | |
| 49 | QQmlFunctionSorter::QQmlFunctionSorter(QObject *parent) |
| 50 | : QQmlSorterBase (new QQmlFunctionSorterPrivate, parent) |
| 51 | { |
| 52 | } |
| 53 | |
| 54 | QQmlFunctionSorter::~QQmlFunctionSorter() |
| 55 | { |
| 56 | Q_D(QQmlFunctionSorter); |
| 57 | if (d->m_lhsParameterData.metaType().flags() & QMetaType::PointerToQObject) |
| 58 | delete d->m_lhsParameterData.value<QObject *>(); |
| 59 | if (d->m_rhsParameterData.metaType().flags() & QMetaType::PointerToQObject) |
| 60 | delete d->m_rhsParameterData.value<QObject *>(); |
| 61 | } |
| 62 | |
| 63 | void QQmlFunctionSorter::componentComplete() |
| 64 | { |
| 65 | Q_D(QQmlFunctionSorter); |
| 66 | const auto *metaObj = this->metaObject(); |
| 67 | for (int idx = metaObj->methodOffset(); idx < metaObj->methodCount(); idx++) { |
| 68 | // Once we find the method signature, break the loop |
| 69 | QMetaMethod method = metaObj->method(index: idx); |
| 70 | if (method.nameView() == "sort" ) { |
| 71 | d->m_method = method; |
| 72 | break; |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | if (!d->m_method.isValid()) |
| 77 | return; |
| 78 | |
| 79 | if (d->m_method.parameterCount() != 2) { |
| 80 | qWarning(msg: "sort method requires two parameters" ); |
| 81 | return; |
| 82 | } |
| 83 | |
| 84 | QQmlData *data = QQmlData::get(object: this); |
| 85 | if (!data || !data->outerContext) { |
| 86 | qWarning(msg: "sort requires a QML context" ); |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | const QMetaType parameterType = d->m_method.parameterMetaType(index: 0); |
| 91 | if (parameterType != d->m_method.parameterMetaType(index: 1)) { |
| 92 | qWarning(msg: "sort parameters have to be equal" ); |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | auto cu = QQmlMetaType::obtainCompilationUnit(type: parameterType); |
| 97 | const QQmlType parameterQmlType = QQmlMetaType::qmlType(metaType: parameterType); |
| 98 | |
| 99 | QQmlRefPointer<QQmlContextData> context = data->outerContext; |
| 100 | QQmlEngine *engine = context->engine(); |
| 101 | |
| 102 | // The code below creates an instance of the inline component, composite, |
| 103 | // or specific C++ QObject types. The created instance, along with the |
| 104 | // data, are passed as an arguments to the 'sort' method, which is invoked |
| 105 | // during the call to QQmlFunctionSorter::compare. |
| 106 | // To create an instance of required component types (be it inline or |
| 107 | // composite), an executable compilation unit is required, and this can be |
| 108 | // obtained by looking up via metatype in the type registry |
| 109 | // (QQmlMetaType::obtainCompilationUnit). Pass it through the QML engine to |
| 110 | // make it executable. Further, use the executable compilation unit to run |
| 111 | // an object creator and produce an instance. |
| 112 | if (parameterType.flags() & QMetaType::PointerToQObject) { |
| 113 | QObject *created0 = nullptr; |
| 114 | QObject *created1 = nullptr; |
| 115 | if (parameterQmlType.isInlineComponentType()) { |
| 116 | const auto executableCu = engine->handle()->executableCompilationUnit(unit: std::move(cu)); |
| 117 | const QString icName = parameterQmlType.elementName(); |
| 118 | created0 = QQmlObjectCreator(context, executableCu, context, icName).create( |
| 119 | subComponentIndex: executableCu->inlineComponentId(inlineComponentName: icName), parent: nullptr, interrupt: nullptr, |
| 120 | flags: QQmlObjectCreator::InlineComponent); |
| 121 | created1 = QQmlObjectCreator(context, executableCu, context, icName).create( |
| 122 | subComponentIndex: executableCu->inlineComponentId(inlineComponentName: icName), parent: nullptr, interrupt: nullptr, |
| 123 | flags: QQmlObjectCreator::InlineComponent); |
| 124 | } else if (parameterQmlType.isComposite()) { |
| 125 | const auto executableCu = engine->handle()->executableCompilationUnit(unit: std::move(cu)); |
| 126 | created0 = QQmlObjectCreator(context, executableCu, context, QString()).create(); |
| 127 | created1 = QQmlObjectCreator(context, executableCu, context, QString()).create(); |
| 128 | } else { |
| 129 | created0 = parameterQmlType.metaObject()->newInstance(); |
| 130 | created1 = parameterQmlType.metaObject()->newInstance(); |
| 131 | } |
| 132 | |
| 133 | const auto names = d->m_method.parameterNames(); |
| 134 | created0->setObjectName(names[0]); |
| 135 | created1->setObjectName(names[1]); |
| 136 | d->m_lhsParameterData = QVariant::fromValue(value: created0); |
| 137 | d->m_rhsParameterData = QVariant::fromValue(value: created1); |
| 138 | } else { |
| 139 | d->m_lhsParameterData = QVariant(parameterType); |
| 140 | d->m_rhsParameterData = QVariant(parameterType); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | /*! |
| 145 | \internal |
| 146 | */ |
| 147 | QPartialOrdering QQmlFunctionSorter::compare( |
| 148 | const QModelIndex& sourceLeft, const QModelIndex& sourceRight, |
| 149 | const QQmlSortFilterProxyModel *proxyModel) const |
| 150 | { |
| 151 | Q_D(const QQmlFunctionSorter); |
| 152 | if (!d->m_method.isValid() |
| 153 | || !d->m_lhsParameterData.isValid() |
| 154 | || !d->m_rhsParameterData.isValid()) { |
| 155 | return QPartialOrdering::Unordered; |
| 156 | } |
| 157 | |
| 158 | int retVal = 0; |
| 159 | QSortFilterProxyModelHelper::setProperties(target: &d->m_lhsParameterData, proxyModel, sourceIndex: sourceLeft); |
| 160 | QSortFilterProxyModelHelper::setProperties(target: &d->m_rhsParameterData, proxyModel, sourceIndex: sourceRight); |
| 161 | |
| 162 | void *argv[] = {&retVal, d->m_lhsParameterData.data(), d->m_rhsParameterData.data()}; |
| 163 | QMetaObject::metacall( |
| 164 | const_cast<QQmlFunctionSorter *>(this), QMetaObject::InvokeMetaMethod, |
| 165 | d->m_method.methodIndex(), argv); |
| 166 | |
| 167 | return (retVal == 0) |
| 168 | ? QPartialOrdering::Equivalent |
| 169 | : ((retVal < 0) ? QPartialOrdering::Less : QPartialOrdering::Greater); |
| 170 | } |
| 171 | |
| 172 | QT_END_NAMESPACE |
| 173 | |
| 174 | #include "moc_qqmlfunctionsorter_p.cpp" |
| 175 | |