Plasma5Support

datamodel.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "datamodel.h"
8#include "datasource.h"
9
10#include <QQmlContext>
11#include <QQmlEngine>
12#include <QTimer>
13
15{
16SortFilterModel::SortFilterModel(QObject *parent)
17 : QSortFilterProxyModel(parent)
18{
19 setObjectName(QStringLiteral("SortFilterModel"));
20 setDynamicSortFilter(true);
21 connect(this, &QAbstractItemModel::rowsInserted, this, &SortFilterModel::countChanged);
22 connect(this, &QAbstractItemModel::rowsRemoved, this, &SortFilterModel::countChanged);
23 connect(this, &QAbstractItemModel::modelReset, this, &SortFilterModel::countChanged);
24 connect(this, &SortFilterModel::countChanged, this, &SortFilterModel::syncRoleNames);
25}
26
27SortFilterModel::~SortFilterModel()
28{
29}
30
31void SortFilterModel::syncRoleNames()
32{
33 if (!sourceModel()) {
34 return;
35 }
36
37 m_roleIds.clear();
38 const QHash<int, QByteArray> rNames = roleNames();
39 m_roleIds.reserve(rNames.count());
40 for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) {
41 m_roleIds[QString::fromUtf8(i.value())] = i.key();
42 }
43
44 setFilterRole(m_filterRole);
45 setSortRole(m_sortRole);
46}
47
48QHash<int, QByteArray> SortFilterModel::roleNames() const
49{
50 if (sourceModel()) {
51 return sourceModel()->roleNames();
52 }
53 return {};
54}
55
56int SortFilterModel::roleNameToId(const QString &name) const
57{
58 return m_roleIds.value(name, Qt::DisplayRole);
59}
60
61void SortFilterModel::setModel(QAbstractItemModel *model)
62{
63 if (model == sourceModel()) {
64 return;
65 }
66
67 if (sourceModel()) {
68 disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames);
69 }
70
72
73 if (model) {
74 connect(model, &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames);
75 syncRoleNames();
76 }
77
79}
80
81bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
82{
83 if (m_filterCallback.isCallable()) {
84 QJSValueList args;
85 args << QJSValue(source_row);
86
87 const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
89 args << engine->toScriptValue<QVariant>(idx.data(m_roleIds.value(m_filterRole)));
90
91 return const_cast<SortFilterModel *>(this)->m_filterCallback.call(args).toBool();
92 }
93
94 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
95}
96
97void SortFilterModel::setFilterRegExp(const QString &exp)
98{
99 if (exp == filterRegExp()) {
100 return;
101 }
103 Q_EMIT filterRegExpChanged(exp);
104}
105
107{
109}
110
111void SortFilterModel::setFilterString(const QString &filterString)
112{
113 if (filterString == m_filterString) {
114 return;
115 }
116 m_filterString = filterString;
118 Q_EMIT filterStringChanged(filterString);
119}
120
122{
123 return m_filterString;
124}
125
127{
128 return m_filterCallback;
129}
130
131void SortFilterModel::setFilterCallback(const QJSValue &callback)
132{
133 if (m_filterCallback.strictlyEquals(callback)) {
134 return;
135 }
136
137 if (!callback.isNull() && !callback.isCallable()) {
138 return;
139 }
140
141 m_filterCallback = callback;
143
144 Q_EMIT filterCallbackChanged(callback);
145}
146
147void SortFilterModel::setFilterRole(const QString &role)
148{
149 QSortFilterProxyModel::setFilterRole(roleNameToId(role));
150 m_filterRole = role;
151}
152
154{
155 return m_filterRole;
156}
157
158void SortFilterModel::setSortRole(const QString &role)
159{
160 m_sortRole = role;
161 if (role.isEmpty()) {
163 } else if (sourceModel()) {
164 QSortFilterProxyModel::setSortRole(roleNameToId(role));
166 }
167}
168
170{
171 return m_sortRole;
172}
173
174void SortFilterModel::setSortOrder(const Qt::SortOrder order)
175{
176 if (order == sortOrder()) {
177 return;
178 }
179 sort(sortColumn(), order);
180}
181
182void SortFilterModel::setSortColumn(int column)
183{
184 if (column == sortColumn()) {
185 return;
186 }
187 sort(column, sortOrder());
188 Q_EMIT sortColumnChanged();
189}
190
191QVariantMap SortFilterModel::get(int row) const
192{
193 QModelIndex idx = index(row, 0);
194 QVariantMap hash;
195
196 const QHash<int, QByteArray> rNames = roleNames();
197 for (auto i = rNames.begin(); i != rNames.end(); ++i) {
198 hash[QString::fromUtf8(i.value())] = data(idx, i.key());
199 }
200
201 return hash;
202}
203
204int SortFilterModel::mapRowToSource(int row) const
205{
206 QModelIndex idx = index(row, 0);
207 return mapToSource(idx).row();
208}
209
210int SortFilterModel::mapRowFromSource(int row) const
211{
212 if (!sourceModel()) {
213 qWarning() << "No source model defined!";
214 return -1;
215 }
216 QModelIndex idx = sourceModel()->index(row, 0);
217 return mapFromSource(idx).row();
218}
219
220DataModel::DataModel(QObject *parent)
221 : QAbstractItemModel(parent)
222 , m_dataSource(nullptr)
223 , m_maxRoleId(Qt::UserRole + 1)
224{
225 // There is one reserved role name: DataEngineSource
226 m_roleNames[m_maxRoleId] = QByteArrayLiteral("DataEngineSource");
227 m_roleIds[QStringLiteral("DataEngineSource")] = m_maxRoleId;
228 ++m_maxRoleId;
229
230 setObjectName(QStringLiteral("DataModel"));
231 connect(this, &QAbstractItemModel::rowsInserted, this, &DataModel::countChanged);
232 connect(this, &QAbstractItemModel::rowsRemoved, this, &DataModel::countChanged);
233 connect(this, &QAbstractItemModel::modelReset, this, &DataModel::countChanged);
234}
235
236DataModel::~DataModel()
237{
238}
239
240static bool isExactMatch(const QRegularExpression &re, const QString &s)
241{
242 const auto match = re.match(s);
243 return match.hasMatch() && s.size() == match.capturedLength();
244}
245
246void DataModel::dataUpdated(const QString &sourceName, const QVariantMap &data)
247{
248 if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !isExactMatch(m_sourceFilterRE, sourceName)) {
249 return;
250 }
251
252 if (m_keyRoleFilter.isEmpty()) {
253 // an item is represented by a source: keys are roles m_roleLevel == FirstLevel
254 QVariantList list;
255
256 if (!m_dataSource->data()->isEmpty()) {
257 const auto lst = m_dataSource->data()->keys();
258 for (const QString &key : lst) {
259 if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !isExactMatch(m_sourceFilterRE, key)) {
260 continue;
261 }
262 QVariant value = m_dataSource->data()->value(key);
263 if (value.isValid() && value.canConvert<Plasma5Support::DataEngine::Data>()) {
265 data[QStringLiteral("DataEngineSource")] = key;
266 list.append(data);
267 }
268 }
269 }
270 setItems(QString(), list);
271 } else {
272 // a key that matches the one we want exists and is a list of DataEngine::Data
273 if (data.contains(m_keyRoleFilter) && data.value(m_keyRoleFilter).canConvert<QVariantList>()) {
274 setItems(sourceName, data.value(m_keyRoleFilter).value<QVariantList>());
275 } else if (m_keyRoleFilterRE.isValid()) {
276 // try to match the key we want with a regular expression if set
277 QVariantList list;
278 QVariantMap::const_iterator i;
279 for (i = data.constBegin(); i != data.constEnd(); ++i) {
280 if (isExactMatch(m_keyRoleFilterRE, i.key())) {
281 list.append(i.value());
282 }
283 }
284 setItems(sourceName, list);
285 }
286 }
287}
288
289void DataModel::setDataSource(QObject *object)
290{
291 DataSource *source = qobject_cast<DataSource *>(object);
292 if (!source) {
293 qWarning() << "Error: DataSource type expected";
294 return;
295 }
296 if (m_dataSource == source) {
297 return;
298 }
299
300 if (m_dataSource) {
301 disconnect(m_dataSource, nullptr, this, nullptr);
302 }
303
304 m_dataSource = source;
305
306 const auto keys = m_dataSource->data()->keys();
307 for (const QString &key : keys) {
308 dataUpdated(key, m_dataSource->data()->value(key).value<Plasma5Support::DataEngine::Data>());
309 }
310
311 connect(m_dataSource, &DataSource::newData, this, &DataModel::dataUpdated);
312 connect(m_dataSource, &DataSource::sourceRemoved, this, &DataModel::removeSource);
313 connect(m_dataSource, &DataSource::sourceDisconnected, this, &DataModel::removeSource);
314}
315
317{
318 return m_dataSource;
319}
320
322{
323 // the "key role filter" can be used in one of three ways:
324 //
325 // 1) empty string -> all data is used, each source is one row in the model
326 // 2) matches a key in the data exactly -> only that key/value pair is used, and the value is
327 // treated as a collection where each item in the collection becomes a row in the model
328 // 3) regular expression -> matches zero or more keys in the data, and each matching key/value
329 // pair becomes a row in the model
330 if (m_keyRoleFilter == key) {
331 return;
332 }
333
334 m_keyRoleFilter = key;
335 m_keyRoleFilterRE = QRegularExpression(m_keyRoleFilter);
336}
337
339{
340 return m_keyRoleFilter;
341}
342
344{
345 if (m_sourceFilter == key) {
346 return;
347 }
348
349 m_sourceFilter = key;
350 m_sourceFilterRE = QRegularExpression(key);
351 /*
352 FIXME: if the user changes the source filter, it won't immediately be reflected in the
353 available data
354 if (m_sourceFilterRE.isValid()) {
355 .. iterate through all items and weed out the ones that don't match ..
356 }
357 */
358}
359
361{
362 return m_sourceFilter;
363}
364
365void DataModel::setItems(const QString &sourceName, const QVariantList &list)
366{
367 const int oldLength = m_items.value(sourceName).count();
368 const int delta = list.length() - oldLength;
369 const bool firstRun = m_items.isEmpty();
370
371 // At what row number the first item associated to this source starts
372 int sourceIndex = 0;
373 QMap<QString, QList<QVariant>>::const_iterator i;
374 for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
375 if (i.key() == sourceName) {
376 break;
377 }
378 sourceIndex += i.value().count();
379 }
380 // signal as inserted the rows at the end, all the other rows will signal a dataupdated.
381 // better than a model reset because doesn't cause deletion and re-creation of every list item on a qml ListView, repeaters etc.
382 // the first run it gets reset because otherwise setRoleNames gets broken
383 if (firstRun) {
385 } else if (delta > 0) {
386 beginInsertRows(QModelIndex(), sourceIndex + oldLength, sourceIndex + list.length() - 1);
387 } else if (delta < 0) {
388 beginRemoveRows(QModelIndex(), sourceIndex + list.length(), sourceIndex + oldLength - 1);
389 }
390 // convert to vector, so data() will be O(1)
391 m_items[sourceName] = list.toVector();
392
393 if (!list.isEmpty()) {
394 for (const QVariant &item : list) {
395 const QVariantMap &vh = item.value<QVariantMap>();
397 while (it.hasNext()) {
398 it.next();
399 const QString &roleName = it.key();
400 if (!m_roleIds.contains(roleName)) {
401 ++m_maxRoleId;
402 m_roleNames[m_maxRoleId] = roleName.toLatin1();
403 m_roleIds[roleName] = m_maxRoleId;
404 }
405 }
406 }
407 }
408
409 if (firstRun) {
411 } else if (delta > 0) {
413 } else if (delta < 0) {
415 }
416 Q_EMIT dataChanged(createIndex(sourceIndex, 0), createIndex(sourceIndex + qMin(list.length(), oldLength), 0));
417}
418
419QHash<int, QByteArray> DataModel::roleNames() const
420{
421 return m_roleNames;
422}
423
424void DataModel::removeSource(const QString &sourceName)
425{
426 // FIXME: find a way to remove only the proper things also in the case where sources are items
427
428 if (m_keyRoleFilter.isEmpty()) {
429 // source name in the map, linear scan
430 for (int i = 0; i < m_items.value(QString()).count(); ++i) {
431 if (m_items.value(QString())[i].value<QVariantMap>().value(QStringLiteral("DataEngineSource")) == sourceName) {
433 m_items[QString()].remove(i);
435 break;
436 }
437 }
438 } else {
439 if (m_items.contains(sourceName)) {
440 // At what row number the first item associated to this source starts
441 int sourceIndex = 0;
442 for (auto i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
443 if (i.key() == sourceName) {
444 break;
445 }
446 sourceIndex += i.value().count();
447 }
448
449 // source name as key of the map
450
451 int count = m_items.value(sourceName).count();
452 if (count > 0) {
453 beginRemoveRows(QModelIndex(), sourceIndex, sourceIndex + count - 1);
454 }
455 m_items.remove(sourceName);
456 if (count > 0) {
458 }
459 }
460 }
461}
462
463QVariant DataModel::data(const QModelIndex &index, int role) const
464{
465 if (!index.isValid() || index.column() > 0 || index.row() < 0 || index.row() >= countItems()) {
466 return QVariant();
467 }
468
469 int count = 0;
470 int actualRow = 0;
471 QString source;
472 QMap<QString, QList<QVariant>>::const_iterator i;
473 for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
474 const int oldCount = count;
475 count += i.value().count();
476
477 if (index.row() < count) {
478 source = i.key();
479 actualRow = index.row() - oldCount;
480 break;
481 }
482 }
483
484 // is it the reserved role: DataEngineSource ?
485 // also, if each source is an item DataEngineSource is a role between all the others, otherwise we know it from the role variable
486 if (!m_keyRoleFilter.isEmpty() && m_roleNames.value(role) == "DataEngineSource") {
487 return source;
488 } else {
489 return m_items.value(source).value(actualRow).value<QVariantMap>().value(QString::fromUtf8(m_roleNames.value(role)));
490 }
491}
492
493QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const
494{
495 Q_UNUSED(section)
496 Q_UNUSED(orientation)
497 Q_UNUSED(role)
498
499 return QVariant();
500}
501
502QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const
503{
504 if (parent.isValid() || column > 0 || row < 0 || row >= countItems()) {
505 return QModelIndex();
506 }
507
508 return createIndex(row, column);
509}
510
511QModelIndex DataModel::parent(const QModelIndex &child) const
512{
513 Q_UNUSED(child)
514
515 return QModelIndex();
516}
517
518int DataModel::rowCount(const QModelIndex &parent) const
519{
520 // this is not a tree
521 // TODO: make it possible some day?
522 if (parent.isValid()) {
523 return 0;
524 }
525
526 return countItems();
527}
528
529int DataModel::columnCount(const QModelIndex &parent) const
530{
531 if (parent.isValid()) {
532 return 0;
533 }
534
535 return 1;
536}
537
538QVariantMap DataModel::get(int row) const
539{
540 QModelIndex idx = index(row, 0);
541 QVariantMap map;
542
543 const QHash<int, QByteArray> rNames = roleNames();
544 for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) {
545 map[QString::fromUtf8(i.value())] = data(idx, i.key());
546 }
547
548 return map;
549}
550
551}
552
553#include "moc_datamodel.cpp"
QString sourceFilter
It's a regular expression.
Definition datamodel.h:181
QML_ELEMENTQObject * dataSource
The instance of DataSource to construct this model on.
Definition datamodel.h:167
void setKeyRoleFilter(const QString &key)
Include only items with a key that matches this regexp in the model.
int count
How many items are in this model.
Definition datamodel.h:186
void setSourceFilter(const QString &key)
Include only sources that matches this regexp in the model.
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
QString keyRoleFilter
It's a regular expression.
Definition datamodel.h:173
QQmlPropertyMap * data
All the data fetched by this dataengine.
Definition datasource.h:121
Filter and sort an existing QAbstractItemModel.
Definition datamodel.h:33
QString sortRole
The role of the sourceModel that will be used for sorting.
Definition datamodel.h:69
QML_ELEMENTQAbstractItemModel * sourceModel
The source model of this sorting proxy model.
Definition datamodel.h:39
int sortColumn
Specify which column should be used for sorting.
Definition datamodel.h:79
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
QString filterRole
The role of the sourceModel on which filterRegExp must be applied.
Definition datamodel.h:64
QString filterRegExp
The regular expression for the filter, only items with their filterRole matching filterRegExp will be...
Definition datamodel.h:44
QJSValue filterCallback
A JavaScript callable that is passed the source model row index as first argument and the value of fi...
Definition datamodel.h:59
QString filterString
The string for the filter, only items with their filterRole matching filterString will be displayed.
Definition datamodel.h:49
Qt::SortOrder sortOrder
One of Qt.Ascending or Qt.Descending.
Definition datamodel.h:74
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Namespace for everything in libplasma.
Definition datamodel.cpp:15
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
iterator begin()
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
qsizetype count() const const
iterator end()
Key key(const T &value) const const
void reserve(qsizetype size)
T value(const Key &key) const const
QJSValue toScriptValue(const T &value)
QJSValue call(const QJSValueList &args) const const
bool isCallable() const const
bool isNull() const const
bool strictlyEquals(const QJSValue &other) const const
bool toBool() const const
QList< T > toVector() const const
void append(QList< T > &&value)
bool isEmpty() const const
qsizetype length() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
bool isEmpty() const const
Key key(const T &value, const Key &defaultKey) const const
size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
int column() const const
QVariant data(int role) const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T qobject_cast(QObject *object)
void setObjectName(QAnyStringView name)
QQmlEngine * engine() const const
QQmlContext * contextForObject(const QObject *object)
bool isEmpty() const const
QStringList keys() const const
QVariant value(const QString &key) const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QString escape(QStringView str)
bool isValid() const const
virtual QVariant data(const QModelIndex &index, int role) const const override
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
void setFilterRegularExpression(const QRegularExpression &regularExpression)
void setFilterRole(int role)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
virtual void sort(int column, Qt::SortOrder order) override
void setSortRole(int role)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
QByteArray toLatin1() const const
DisplayRole
Orientation
AscendingOrder
bool canConvert() const const
bool isValid() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.