| 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/qqmlfunctionfilter_p.h> |
| 5 | #include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h> |
| 6 | #include <QtQml/private/qqmlobjectcreator_p.h> |
| 7 | #include <QObject> |
| 8 | #include <QMetaMethod> |
| 9 | |
| 10 | QT_BEGIN_NAMESPACE |
| 11 | |
| 12 | /*! |
| 13 | \qmltype FunctionFilter |
| 14 | \inherits Filter |
| 15 | \inqmlmodule QtQml.Models |
| 16 | \since 6.10 |
| 17 | \preliminary |
| 18 | \brief Filters data in a \l SortFilterProxyModel based on the evaluation |
| 19 | of the designated 'filter' method. |
| 20 | |
| 21 | FunctionFilter allows user to define the designated 'filter' method and it |
| 22 | will be evaluated to filter the data. The 'filter' method takes one |
| 23 | argument and it can be defined as inline component as below: |
| 24 | |
| 25 | \qml |
| 26 | SortFilterProxyModel { |
| 27 | model: sourceModel |
| 28 | filters: [ |
| 29 | FunctionFilter { |
| 30 | id: functionFilter |
| 31 | property int ageLimit: 20 |
| 32 | component RoleData: QtObject { |
| 33 | property real age |
| 34 | } |
| 35 | function filter(data: RoleData) : bool { |
| 36 | return (data.age <= ageLimit) |
| 37 | } |
| 38 | } |
| 39 | ] |
| 40 | } |
| 41 | \endqml |
| 42 | |
| 43 | \note The user needs to explicitly invoke |
| 44 | \l{SortFilterProxyModel::invalidate} whenever any external qml property |
| 45 | used within the designated 'filter' method changes. This behaviour is |
| 46 | subject to change in the future, like implicit invalidation and thus the |
| 47 | user doesn't need to explicitly invoke |
| 48 | \l{SortFilterProxyModel::invalidate}. |
| 49 | */ |
| 50 | |
| 51 | QQmlFunctionFilter::QQmlFunctionFilter(QObject *parent) |
| 52 | : QQmlFilterBase (new QQmlFunctionFilterPrivate, parent) |
| 53 | { |
| 54 | } |
| 55 | |
| 56 | QQmlFunctionFilter::~QQmlFunctionFilter() |
| 57 | { |
| 58 | Q_D(QQmlFunctionFilter); |
| 59 | if (d->m_parameterData.metaType().flags() & QMetaType::PointerToQObject) |
| 60 | delete d->m_parameterData.value<QObject *>(); |
| 61 | } |
| 62 | |
| 63 | void QQmlFunctionFilter::componentComplete() |
| 64 | { |
| 65 | Q_D(QQmlFunctionFilter); |
| 66 | const auto *metaObj = 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() == "filter" ) { |
| 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() != 1) { |
| 80 | qWarning(msg: "filter method requires a single parameter" ); |
| 81 | return; |
| 82 | } |
| 83 | |
| 84 | QQmlData *data = QQmlData::get(object: this); |
| 85 | if (!data || !data->outerContext) { |
| 86 | qWarning(msg: "filter requires a QML context" ); |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | QQmlRefPointer<QQmlContextData> context = data->outerContext; |
| 91 | QQmlEngine *engine = context->engine(); |
| 92 | |
| 93 | const QMetaType parameterType = d->m_method.parameterMetaType(index: 0); |
| 94 | auto cu = QQmlMetaType::obtainCompilationUnit(type: parameterType); |
| 95 | const QQmlType parameterQmlType = QQmlMetaType::qmlType(metaType: parameterType); |
| 96 | |
| 97 | if (!parameterQmlType.isValid()) { |
| 98 | qWarning(msg: "filter method parameter needs to be a QML-registered type" ); |
| 99 | return; |
| 100 | } |
| 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, is passed as an argument to the 'filter' method, which is invoked |
| 105 | // during the call to QQmlFunctionFilter::filterAcceptsRowInternal. |
| 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 *created = nullptr; |
| 114 | if (parameterQmlType.isInlineComponentType()) { |
| 115 | const auto executableCu = engine->handle()->executableCompilationUnit(unit: std::move(cu)); |
| 116 | const QString icName = parameterQmlType.elementName(); |
| 117 | created = QQmlObjectCreator(context, executableCu, context, icName).create( |
| 118 | subComponentIndex: executableCu->inlineComponentId(inlineComponentName: icName), parent: nullptr, interrupt: nullptr, |
| 119 | flags: QQmlObjectCreator::InlineComponent); |
| 120 | } else if (parameterQmlType.isComposite()) { |
| 121 | const auto executableCu = engine->handle()->executableCompilationUnit(unit: std::move(cu)); |
| 122 | created = QQmlObjectCreator(context, executableCu, context, QString()).create(); |
| 123 | } else { |
| 124 | created = parameterQmlType.metaObject()->newInstance(); |
| 125 | } |
| 126 | |
| 127 | const auto names = d->m_method.parameterNames(); |
| 128 | created->setObjectName(names[0]); |
| 129 | d->m_parameterData = QVariant::fromValue(value: created); |
| 130 | } else { |
| 131 | d->m_parameterData = QVariant(parameterType); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | /*! |
| 136 | \internal |
| 137 | */ |
| 138 | bool QQmlFunctionFilter::filterAcceptsRowInternal(int row, const QModelIndex& sourceParent, const QQmlSortFilterProxyModel *proxyModel) const |
| 139 | { |
| 140 | Q_D(const QQmlFunctionFilter); |
| 141 | if (!d->m_method.isValid() || !d->m_parameterData.isValid()) |
| 142 | return true; |
| 143 | |
| 144 | bool retVal = false; |
| 145 | if (column() > -1) { |
| 146 | QSortFilterProxyModelHelper::setProperties( |
| 147 | target: &d->m_parameterData, proxyModel, |
| 148 | sourceIndex: proxyModel->sourceModel()->index(row, column: column(), parent: sourceParent)); |
| 149 | void *argv[] = {&retVal, d->m_parameterData.data()}; |
| 150 | QMetaObject::metacall( |
| 151 | const_cast<QQmlFunctionFilter *>(this), QMetaObject::InvokeMetaMethod, |
| 152 | d->m_method.methodIndex(), argv); |
| 153 | } else { |
| 154 | const int columnCount = proxyModel->sourceModel()->columnCount(parent: sourceParent); |
| 155 | for (int column = 0; column < columnCount; column++) { |
| 156 | QSortFilterProxyModelHelper::setProperties( |
| 157 | target: &d->m_parameterData, proxyModel, |
| 158 | sourceIndex: proxyModel->sourceModel()->index(row, column, parent: sourceParent)); |
| 159 | void *argv[] = {&retVal, d->m_parameterData.data()}; |
| 160 | QMetaObject::metacall( |
| 161 | const_cast<QQmlFunctionFilter *>(this), QMetaObject::InvokeMetaMethod, |
| 162 | d->m_method.methodIndex(), argv); |
| 163 | if (retVal) |
| 164 | return retVal; |
| 165 | } |
| 166 | } |
| 167 | return retVal; |
| 168 | } |
| 169 | |
| 170 | QT_END_NAMESPACE |
| 171 | |
| 172 | #include "moc_qqmlfunctionfilter_p.cpp" |
| 173 | |