1/*
2 * SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "ksortfilterproxymodel.h"
9
10#include <QQmlContext>
11#include <QQmlEngine>
12
13#include "kitemmodels_debug.h"
14
15KSortFilterProxyModel::KSortFilterProxyModel(QObject *parent)
16 : QSortFilterProxyModel(parent)
17 , m_componentCompleted(false)
18 , m_sortRoleSourceOfTruth(SourceOfTruthIsRoleID)
19 , m_filterRoleSourceOfTruth(SourceOfTruthIsRoleID)
20 , m_sortRoleGuard(false)
21 , m_filterRoleGuard(false)
22{
23 setDynamicSortFilter(true);
24 connect(sender: this, signal: &KSortFilterProxyModel::modelReset, context: this, slot: &KSortFilterProxyModel::rowCountChanged);
25 connect(sender: this, signal: &KSortFilterProxyModel::rowsInserted, context: this, slot: &KSortFilterProxyModel::rowCountChanged);
26 connect(sender: this, signal: &KSortFilterProxyModel::rowsRemoved, context: this, slot: &KSortFilterProxyModel::rowCountChanged);
27
28 connect(sender: this, signal: &KSortFilterProxyModel::sortRoleChanged, context: this, slot: &KSortFilterProxyModel::syncSortRoleProperties);
29 connect(sender: this, signal: &KSortFilterProxyModel::filterRoleChanged, context: this, slot: &KSortFilterProxyModel::syncFilterRoleProperties);
30}
31
32KSortFilterProxyModel::~KSortFilterProxyModel()
33{
34}
35
36static void reverseStringIntHash(QHash<QString, int> &dst, const QHash<int, QByteArray> &src)
37{
38 dst.clear();
39 dst.reserve(size: src.count());
40 for (auto i = src.constBegin(); i != src.constEnd(); ++i) {
41 dst[QString::fromUtf8(ba: i.value())] = i.key();
42 }
43}
44
45void KSortFilterProxyModel::syncRoleNames()
46{
47 if (!sourceModel()) {
48 return;
49 }
50
51 reverseStringIntHash(dst&: m_roleIds, src: roleNames());
52
53 m_sortRoleGuard = true;
54 syncSortRoleProperties();
55 m_sortRoleGuard = false;
56
57 m_filterRoleGuard = true;
58 syncFilterRoleProperties();
59 m_filterRoleGuard = false;
60}
61
62int KSortFilterProxyModel::roleNameToId(const QString &name) const
63{
64 return m_roleIds.value(key: name, defaultValue: Qt::DisplayRole);
65}
66
67void KSortFilterProxyModel::setSourceModel(QAbstractItemModel *model)
68{
69 const auto oldModel = sourceModel();
70
71 if (model == oldModel) {
72 return;
73 }
74
75 if (oldModel) {
76 for (const auto &connection : std::as_const(t&: m_sourceModelConnections)) {
77 disconnect(connection);
78 }
79 }
80
81 QSortFilterProxyModel::setSourceModel(model);
82
83 // NOTE: some models actually fill their roleNames() only when they get some actual data, this works around the bad behavior
84 if (model) {
85 m_sourceModelConnections = {._M_elems: {
86 connect(sender: model, signal: &QAbstractItemModel::modelReset, context: this, slot: &KSortFilterProxyModel::syncRoleNames),
87 connect(sender: model, signal: &QAbstractItemModel::rowsInserted, context: this, slot: &KSortFilterProxyModel::syncRoleNames),
88 connect(sender: model, signal: &QAbstractItemModel::rowsRemoved, context: this, slot: &KSortFilterProxyModel::syncRoleNames),
89 }};
90 }
91
92 if (m_componentCompleted) {
93 syncRoleNames();
94 }
95}
96
97bool KSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
98{
99 if (m_filterRowCallback.isCallable()) {
100 QJSEngine *engine = qjsEngine(this);
101 QJSValueList args = {QJSValue(source_row), engine->toScriptValue(value: source_parent)};
102
103 QJSValue result = const_cast<KSortFilterProxyModel *>(this)->m_filterRowCallback.call(args);
104 if (result.isError()) {
105 qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:";
106 qCWarning(KITEMMODELS_LOG) << result.toString();
107 return true;
108 } else {
109 return result.toBool();
110 }
111 }
112
113 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
114}
115
116bool KSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
117{
118 if (m_filterColumnCallback.isCallable()) {
119 QJSEngine *engine = qjsEngine(this);
120 QJSValueList args = {QJSValue(source_column), engine->toScriptValue(value: source_parent)};
121
122 QJSValue result = const_cast<KSortFilterProxyModel *>(this)->m_filterColumnCallback.call(args);
123 if (result.isError()) {
124 qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:";
125 qCWarning(KITEMMODELS_LOG) << result.toString();
126 return true;
127 } else {
128 return result.toBool();
129 }
130 }
131
132 return QSortFilterProxyModel::filterAcceptsColumn(source_column, source_parent);
133}
134
135void KSortFilterProxyModel::setFilterString(const QString &filterString)
136{
137 if (filterString == m_filterString) {
138 return;
139 }
140 m_filterString = filterString;
141 QSortFilterProxyModel::setFilterFixedString(filterString);
142 Q_EMIT filterStringChanged();
143}
144
145QString KSortFilterProxyModel::filterString() const
146{
147 return m_filterString;
148}
149
150QJSValue KSortFilterProxyModel::filterRowCallback() const
151{
152 return m_filterRowCallback;
153}
154
155void KSortFilterProxyModel::setFilterRowCallback(const QJSValue &callback)
156{
157 if (m_filterRowCallback.strictlyEquals(other: callback)) {
158 return;
159 }
160
161 if (!callback.isNull() && !callback.isCallable()) {
162 return;
163 }
164
165 m_filterRowCallback = callback;
166 invalidateFilter();
167
168 Q_EMIT filterRowCallbackChanged(callback);
169}
170
171void KSortFilterProxyModel::setFilterColumnCallback(const QJSValue &callback)
172{
173 if (m_filterColumnCallback.strictlyEquals(other: callback)) {
174 return;
175 }
176
177 if (!callback.isNull() && !callback.isCallable()) {
178 return;
179 }
180
181 m_filterColumnCallback = callback;
182 invalidateFilter();
183
184 Q_EMIT filterColumnCallbackChanged(callback);
185}
186
187QJSValue KSortFilterProxyModel::filterColumnCallback() const
188{
189 return m_filterColumnCallback;
190}
191
192void KSortFilterProxyModel::syncSortRoleProperties()
193{
194 if (!sourceModel()) {
195 return;
196 }
197
198 if (!m_sortRoleGuard) {
199 m_sortRoleSourceOfTruth = SourceOfTruthIsRoleID;
200 }
201
202 if (m_sortRoleSourceOfTruth == SourceOfTruthIsRoleName) {
203 if (m_sortRoleName.isEmpty()) {
204 QSortFilterProxyModel::setSortRole(Qt::DisplayRole);
205 sort(column: -1, order: Qt::AscendingOrder);
206 } else {
207 const auto role = roleNameToId(name: m_sortRoleName);
208 QSortFilterProxyModel::setSortRole(role);
209 sort(column: std::max(a: sortColumn(), b: 0), order: sortOrder());
210 }
211 } else {
212 const QString roleName = QString::fromUtf8(ba: roleNames().value(key: sortRole()));
213 if (m_sortRoleName != roleName) {
214 m_sortRoleName = roleName;
215 Q_EMIT sortRoleNameChanged();
216 }
217 }
218}
219
220void KSortFilterProxyModel::syncFilterRoleProperties()
221{
222 if (!sourceModel()) {
223 return;
224 }
225
226 if (!m_filterRoleGuard) {
227 m_filterRoleSourceOfTruth = SourceOfTruthIsRoleID;
228 }
229
230 if (m_filterRoleSourceOfTruth == SourceOfTruthIsRoleName) {
231 const auto role = roleNameToId(name: m_filterRoleName);
232 QSortFilterProxyModel::setFilterRole(role);
233 } else {
234 const QString roleName = QString::fromUtf8(ba: roleNames().value(key: filterRole()));
235 if (m_filterRoleName != roleName) {
236 m_filterRoleName = roleName;
237 Q_EMIT filterRoleNameChanged();
238 }
239 }
240}
241
242void KSortFilterProxyModel::setFilterRoleName(const QString &roleName)
243{
244 if (m_filterRoleSourceOfTruth == SourceOfTruthIsRoleName && m_filterRoleName == roleName) {
245 return;
246 }
247
248 m_filterRoleSourceOfTruth = SourceOfTruthIsRoleName;
249 m_filterRoleName = roleName;
250
251 m_filterRoleGuard = true;
252 syncFilterRoleProperties();
253 m_filterRoleGuard = false;
254
255 Q_EMIT filterRoleNameChanged();
256}
257
258QString KSortFilterProxyModel::filterRoleName() const
259{
260 return m_filterRoleName;
261}
262
263void KSortFilterProxyModel::setSortRoleName(const QString &roleName)
264{
265 if (m_sortRoleSourceOfTruth == SourceOfTruthIsRoleName && m_sortRoleName == roleName) {
266 return;
267 }
268
269 m_sortRoleSourceOfTruth = SourceOfTruthIsRoleName;
270 m_sortRoleName = roleName;
271
272 m_sortRoleGuard = true;
273 syncSortRoleProperties();
274 m_sortRoleGuard = false;
275
276 Q_EMIT sortRoleNameChanged();
277}
278
279QString KSortFilterProxyModel::sortRoleName() const
280{
281 return m_sortRoleName;
282}
283
284void KSortFilterProxyModel::setSortOrder(const Qt::SortOrder order)
285{
286 sort(column: std::max(a: sortColumn(), b: 0), order);
287 Q_EMIT sortOrderChanged();
288}
289
290void KSortFilterProxyModel::setSortColumn(int column)
291{
292 if (column == sortColumn()) {
293 return;
294 }
295 sort(column, order: sortOrder());
296 Q_EMIT sortColumnChanged();
297}
298
299void KSortFilterProxyModel::classBegin()
300{
301}
302
303void KSortFilterProxyModel::componentComplete()
304{
305 m_componentCompleted = true;
306 syncRoleNames();
307}
308
309void KSortFilterProxyModel::invalidateFilter()
310{
311 QSortFilterProxyModel::invalidateFilter();
312}
313
314#include "moc_ksortfilterproxymodel.cpp"
315

source code of kitemmodels/src/qml/ksortfilterproxymodel.cpp