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

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