Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlfunctionsorter.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
5#include <QtQmlModels/private/qqmlfunctionsorter_p.h>
6#include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h>
7#include <QtQml/private/qqmlobjectcreator_p.h>
8#include <QtQml/qqmlinfo.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype FunctionSorter
14 \inherits Sorter
15 \inqmlmodule QtQml.Models
16 \since 6.10
17 \preliminary
18 \brief Sorts data in a \l SortFilterProxyModel based on the evaluation of
19 the designated 'compare' method.
20
21 FunctionSorter allows user to define the designated 'compare' method and it
22 will be evaluated to sort the data. The method takes two arguments
23 (lhs and rhs) of the specified parameter type and the data can
24 be accessed as below for evaluation,
25
26 \qml
27 SortFilterProxyModel {
28 sourceModel: model
29 sorters: [
30 FunctionSorter {
31 id: functionSorter
32 component RoleData: QtObject {
33 property real age
34 }
35 function compare(lhsData: RoleData, rhsData: RoleData) : int {
36 return (lhsData.age < rhsData.age) ? -1 : ((lhsData === rhsData.age) ? 0 : 1)
37 }
38 }
39 ]
40 }
41 \endqml
42
43 \warning Both parameters of the \c compare method must carry an explicit
44 type annotation, as \c lhsData and \c rhsData do in the example above, and
45 both annotations must resolve to the same type. The \c compare method written
46 the ordinary JavaScript way, without parameter type annotations, e.g.
47 \c{function compare(lhsData, rhsData) : int { ... }}, compiles without
48 any warning but fails with comparision. \l FunctionSorter relies on the annotated
49 type to know what kind of role-data object to instantiate and populate for
50 \c lhsData and \c rhsData; without it, there is nothing to instantiate.
51
52 \note \deprecated [6.12] The \c sort method is deprecated. Rename it to \c compare.
53
54 \note The \c compare function must have exactly two explicitly type-annotated
55 parameters of matching types and an explicit \c{: int} return type. If these
56 requirements are not met, the sorter emits a warning and returns early.
57
58 \note The user needs to explicitly invoke
59 \l{SortFilterProxyModel::invalidateSorter} whenever any external qml
60 property used within the designated 'compare' method changes. This behaviour
61 is subject to change in the future, like implicit invalidation and thus the
62 user doesn't need to explicitly invoke
63 \l{SortFilterProxyModel::invalidateSorter}.
64*/
65
66QQmlFunctionSorter::QQmlFunctionSorter(QObject *parent)
67 : QQmlSorterBase (new QQmlFunctionSorterPrivate, parent)
68{
69}
70
71QQmlFunctionSorter::~QQmlFunctionSorter()
72{
73}
74
75void QQmlFunctionSorter::componentComplete()
76{
77 Q_D(QQmlFunctionSorter);
78 const auto *metaObj = this->metaObject();
79 for (int idx = metaObj->methodCount() - 1; idx >= 0; idx--) {
80 // Once we find the method signature, break the loop
81 QMetaMethod method = metaObj->method(idx);
82 if (method.nameView() == "compare") {
83 d->m_method = method;
84 break;
85 } else if (method.nameView() == "sort") {
86 qmlWarning(this) << "The 'sort' method is deprecated and will be removed in a future version. Rename it to 'compare'.";
87 d->m_method = method;
88 break;
89 }
90 }
91
92 if (!d->m_method.isValid())
93 return;
94
95 if (d->m_method.parameterCount() != 2) {
96 qmlWarning(this) << d->m_method.name() << " requires two parameters.";
97 return;
98 }
99
100 QQmlData *data = QQmlData::get(this);
101 if (!data || !data->outerContext) {
102 qmlWarning(this) << d->m_method.name() << " requires a QML context.";
103 return;
104 }
105
106 const QMetaType lhsParameterType = d->m_method.parameterMetaType(0);
107 const QMetaType rhsParameterType = d->m_method.parameterMetaType(1);
108
109 if (!lhsParameterType.isValid() || !rhsParameterType.isValid()) {
110 qmlWarning(this) << d->m_method.name() << " parameters need to be a QML-registered type.";
111 return;
112 }
113
114 if (lhsParameterType != rhsParameterType) {
115 qmlWarning(this) << d->m_method.name() << " parameters need to have matching types.";
116 return;
117 }
118
119 if (lhsParameterType == QMetaType::fromType<QVariant>()) {
120 qmlWarning(this) << d->m_method.name() << " parameters must be type annotated.";
121 return;
122 }
123
124 const QMetaType returnType = d->m_method.returnMetaType();
125 if (returnType != QMetaType::fromType<int>()) {
126 qmlWarning(this) << d->m_method.name() << " must return int data type.";
127 return;
128 }
129
130 auto cu = QQmlMetaType::obtainCompilationUnit(lhsParameterType);
131 const QQmlType parameterQmlType = QQmlMetaType::qmlType(lhsParameterType);
132
133 QQmlRefPointer<QQmlContextData> context = data->outerContext;
134 QQmlEngine *engine = context->engine();
135
136 // The code below creates an instance of the inline component, composite,
137 // or specific C++ QObject types. The created instance, along with the
138 // data, are passed as an arguments to the 'compare' method, which is invoked
139 // during the call to QQmlFunctionSorter::compare.
140 // To create an instance of required component types (be it inline or
141 // composite), an executable compilation unit is required, and this can be
142 // obtained by looking up via metatype in the type registry
143 // (QQmlMetaType::obtainCompilationUnit). Pass it through the QML engine to
144 // make it executable. Further, use the executable compilation unit to run
145 // an object creator and produce an instance.
146 if (lhsParameterType.flags() & QMetaType::PointerToQObject) {
147 QObject *created0 = nullptr;
148 QObject *created1 = nullptr;
149 if (parameterQmlType.isInlineComponent()) {
150 const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
151 const QString icName = parameterQmlType.elementName();
152 created0 = QQmlObjectCreator(context, executableCu, context, icName).create(
153 executableCu->inlineComponentId(icName), nullptr, nullptr,
154 QQmlObjectCreator::InlineComponent);
155 created1 = QQmlObjectCreator(context, executableCu, context, icName).create(
156 executableCu->inlineComponentId(icName), nullptr, nullptr,
157 QQmlObjectCreator::InlineComponent);
158 } else if (parameterQmlType.isComposite()) {
159 const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
160 created0 = QQmlObjectCreator(context, executableCu, context, QString()).create();
161 created1 = QQmlObjectCreator(context, executableCu, context, QString()).create();
162 } else {
163 created0 = parameterQmlType.metaObject()->newInstance();
164 created1 = parameterQmlType.metaObject()->newInstance();
165 }
166
167 if (!created0 || !created1) {
168 qmlWarning(this) << d->m_method.name() << " could not create its parameter objects.";
169 delete created0;
170 delete created1;
171 return;
172 }
173
174 QQmlEngine::setObjectOwnership(created0, QQmlEngine::CppOwnership);
175 QQmlEngine::setObjectOwnership(created1, QQmlEngine::CppOwnership);
176 created0->setParent(this);
177 created1->setParent(this);
178
179 const auto names = d->m_method.parameterNames();
180 created0->setObjectName(names[0]);
181 created1->setObjectName(names[1]);
182 d->m_lhsParameterData = QVariant::fromValue(created0);
183 d->m_rhsParameterData = QVariant::fromValue(created1);
184 } else {
185 d->m_lhsParameterData = QVariant(lhsParameterType);
186 d->m_rhsParameterData = QVariant(rhsParameterType);
187 }
188}
189
190/*!
191 \internal
192*/
193QPartialOrdering QQmlFunctionSorter::compare(
194 const QModelIndex& sourceLeft, const QModelIndex& sourceRight,
195 const QQmlSortFilterProxyModel *proxyModel) const
196{
197 Q_D(const QQmlFunctionSorter);
198 if (!d->m_method.isValid()
199 || !d->m_lhsParameterData.isValid()
200 || !d->m_rhsParameterData.isValid()) {
201 return QPartialOrdering::Unordered;
202 }
203
204 int retVal = 0;
205 QSortFilterProxyModelHelper::setProperties(&d->m_lhsParameterData, proxyModel, sourceLeft);
206 QSortFilterProxyModelHelper::setProperties(&d->m_rhsParameterData, proxyModel, sourceRight);
207
208 void *argv[] = {&retVal, d->m_lhsParameterData.data(), d->m_rhsParameterData.data()};
209 QMetaObject::metacall(
210 const_cast<QQmlFunctionSorter *>(this), QMetaObject::InvokeMetaMethod,
211 d->m_method.methodIndex(), argv);
212
213 return (retVal == 0)
214 ? QPartialOrdering::Equivalent
215 : ((retVal < 0) ? QPartialOrdering::Less : QPartialOrdering::Greater);
216}
217
218QT_END_NAMESPACE
219
220#include "moc_qqmlfunctionsorter_p.cpp"