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 | |
28 | KSortFilterProxyModel::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 | |
45 | KSortFilterProxyModel::~KSortFilterProxyModel() |
46 | { |
47 | } |
48 | |
49 | static 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 | |
58 | void 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 | |
75 | int KSortFilterProxyModel::roleNameToId(const QString &name) const |
76 | { |
77 | return m_roleIds.value(key: name, defaultValue: Qt::DisplayRole); |
78 | } |
79 | |
80 | void 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 | |
110 | bool 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 | |
129 | bool 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 | |
148 | void 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 | |
158 | QString KSortFilterProxyModel::filterString() const |
159 | { |
160 | return m_filterString; |
161 | } |
162 | |
163 | QJSValue KSortFilterProxyModel::filterRowCallback() const |
164 | { |
165 | return m_filterRowCallback; |
166 | } |
167 | |
168 | void 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 | |
184 | void 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 | |
200 | QJSValue KSortFilterProxyModel::filterColumnCallback() const |
201 | { |
202 | return m_filterColumnCallback; |
203 | } |
204 | |
205 | void 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 | |
233 | void 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 | |
255 | void 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 | |
271 | QString KSortFilterProxyModel::filterRoleName() const |
272 | { |
273 | return m_filterRoleName; |
274 | } |
275 | |
276 | void 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 | |
292 | QString KSortFilterProxyModel::sortRoleName() const |
293 | { |
294 | return m_sortRoleName; |
295 | } |
296 | |
297 | void KSortFilterProxyModel::setSortOrder(const Qt::SortOrder order) |
298 | { |
299 | sort(column: std::max(a: sortColumn(), b: 0), order); |
300 | Q_EMIT sortOrderChanged(); |
301 | } |
302 | |
303 | void KSortFilterProxyModel::setSortColumn(int column) |
304 | { |
305 | if (column == sortColumn()) { |
306 | return; |
307 | } |
308 | sort(column, order: sortOrder()); |
309 | Q_EMIT sortColumnChanged(); |
310 | } |
311 | |
312 | void KSortFilterProxyModel::classBegin() |
313 | { |
314 | } |
315 | |
316 | void KSortFilterProxyModel::componentComplete() |
317 | { |
318 | m_componentCompleted = true; |
319 | syncRoleNames(); |
320 | } |
321 | |
322 | void KSortFilterProxyModel::invalidateFilter() |
323 | { |
324 | QSortFilterProxyModel::invalidateFilter(); |
325 | } |
326 | |
327 | #include "moc_ksortfilterproxymodel.cpp" |
328 | |