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 Q_D(QQmlFunctionSorter);
74 if (d->m_lhsParameterData.metaType().flags() & QMetaType::PointerToQObject)
75 delete d->m_lhsParameterData.value<QObject *>();
76 if (d->m_rhsParameterData.metaType().flags() & QMetaType::PointerToQObject)
77 delete d->m_rhsParameterData.value<QObject *>();
78}
79
80void QQmlFunctionSorter::componentComplete()
81{
82 Q_D(QQmlFunctionSorter);
83 const auto *metaObj = this->metaObject();
84 for (int idx = metaObj->methodCount() - 1; idx >= 0; idx--) {
85 // Once we find the method signature, break the loop
86 QMetaMethod method = metaObj->method(idx);
87 if (method.nameView() == "compare") {
88 d->m_method = method;
89 break;
90 } else if (method.nameView() == "sort") {
91 qmlWarning(this) << "The 'sort' method is deprecated and will be removed in a future version. Rename it to 'compare'.";
92 d->m_method = method;
93 break;
94 }
95 }
96
97 if (!d->m_method.isValid())
98 return;
99
100 if (d->m_method.parameterCount() != 2) {
101 qmlWarning(this) << d->m_method.name() << " requires two parameters.";
102 return;
103 }
104
105 QQmlData *data = QQmlData::get(this);
106 if (!data || !data->outerContext) {
107 qmlWarning(this) << d->m_method.name() << " requires a QML context.";
108 return;
109 }
110
111 const QMetaType lhsParameterType = d->m_method.parameterMetaType(0);
112 const QMetaType rhsParameterType = d->m_method.parameterMetaType(1);
113
114 if (!lhsParameterType.isValid() || !rhsParameterType.isValid()) {
115 qmlWarning(this) << d->m_method.name() << " parameters need to be a QML-registered type.";
116 return;
117 }
118
119 if (lhsParameterType != rhsParameterType) {
120 qmlWarning(this) << d->m_method.name() << " parameters need to have matching types.";
121 return;
122 }
123
124 if (lhsParameterType == QMetaType::fromType<QVariant>()) {
125 qmlWarning(this) << d->m_method.name() << " parameters must be type annotated.";
126 return;
127 }
128
129 const QMetaType returnType = d->m_method.returnMetaType();
130 if (returnType != QMetaType::fromType<int>()) {
131 qmlWarning(this) << d->m_method.name() << " must return int data type.";
132 return;
133 }
134
135 auto cu = QQmlMetaType::obtainCompilationUnit(lhsParameterType);
136 const QQmlType parameterQmlType = QQmlMetaType::qmlType(lhsParameterType);
137
138 QQmlRefPointer<QQmlContextData> context = data->outerContext;
139 QQmlEngine *engine = context->engine();
140
141 // The code below creates an instance of the inline component, composite,
142 // or specific C++ QObject types. The created instance, along with the
143 // data, are passed as an arguments to the 'compare' method, which is invoked
144 // during the call to QQmlFunctionSorter::compare.
145 // To create an instance of required component types (be it inline or
146 // composite), an executable compilation unit is required, and this can be
147 // obtained by looking up via metatype in the type registry
148 // (QQmlMetaType::obtainCompilationUnit). Pass it through the QML engine to
149 // make it executable. Further, use the executable compilation unit to run
150 // an object creator and produce an instance.
151 if (lhsParameterType.flags() & QMetaType::PointerToQObject) {
152 QObject *created0 = nullptr;
153 QObject *created1 = nullptr;
154 if (parameterQmlType.isInlineComponent()) {
155 const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
156 const QString icName = parameterQmlType.elementName();
157 created0 = QQmlObjectCreator(context, executableCu, context, icName).create(
158 executableCu->inlineComponentId(icName), nullptr, nullptr,
159 QQmlObjectCreator::InlineComponent);
160 created1 = QQmlObjectCreator(context, executableCu, context, icName).create(
161 executableCu->inlineComponentId(icName), nullptr, nullptr,
162 QQmlObjectCreator::InlineComponent);
163 } else if (parameterQmlType.isComposite()) {
164 const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
165 created0 = QQmlObjectCreator(context, executableCu, context, QString()).create();
166 created1 = QQmlObjectCreator(context, executableCu, context, QString()).create();
167 } else {
168 created0 = parameterQmlType.metaObject()->newInstance();
169 created1 = parameterQmlType.metaObject()->newInstance();
170 }
171
172 const auto names = d->m_method.parameterNames();
173 created0->setObjectName(names[0]);
174 created1->setObjectName(names[1]);
175 d->m_lhsParameterData = QVariant::fromValue(created0);
176 d->m_rhsParameterData = QVariant::fromValue(created1);
177 } else {
178 d->m_lhsParameterData = QVariant(lhsParameterType);
179 d->m_rhsParameterData = QVariant(rhsParameterType);
180 }
181}
182
183/*!
184 \internal
185*/
186QPartialOrdering QQmlFunctionSorter::compare(
187 const QModelIndex& sourceLeft, const QModelIndex& sourceRight,
188 const QQmlSortFilterProxyModel *proxyModel) const
189{
190 Q_D(const QQmlFunctionSorter);
191 if (!d->m_method.isValid()
192 || !d->m_lhsParameterData.isValid()
193 || !d->m_rhsParameterData.isValid()) {
194 return QPartialOrdering::Unordered;
195 }
196
197 int retVal = 0;
198 QSortFilterProxyModelHelper::setProperties(&d->m_lhsParameterData, proxyModel, sourceLeft);
199 QSortFilterProxyModelHelper::setProperties(&d->m_rhsParameterData, proxyModel, sourceRight);
200
201 void *argv[] = {&retVal, d->m_lhsParameterData.data(), d->m_rhsParameterData.data()};
202 QMetaObject::metacall(
203 const_cast<QQmlFunctionSorter *>(this), QMetaObject::InvokeMetaMethod,
204 d->m_method.methodIndex(), argv);
205
206 return (retVal == 0)
207 ? QPartialOrdering::Equivalent
208 : ((retVal < 0) ? QPartialOrdering::Less : QPartialOrdering::Greater);
209}
210
211QT_END_NAMESPACE
212
213#include "moc_qqmlfunctionsorter_p.cpp"