Plasma

datamodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Marco Martin <[email protected]>
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 
14 namespace Plasma
15 {
16 SortFilterModel::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 
27 SortFilterModel::~SortFilterModel()
28 {
29 }
30 
31 void 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 
48 QHash<int, QByteArray> SortFilterModel::roleNames() const
49 {
50  if (sourceModel()) {
51  return sourceModel()->roleNames();
52  }
53  return {};
54 }
55 
56 int SortFilterModel::roleNameToId(const QString &name) const
57 {
58  return m_roleIds.value(name, Qt::DisplayRole);
59 }
60 
61 void 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 
81 bool 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 
97 void SortFilterModel::setFilterRegExp(const QString &exp)
98 {
99  if (exp == filterRegExp()) {
100  return;
101  }
103  Q_EMIT filterRegExpChanged(exp);
104 }
105 
107 {
109 }
110 
111 void 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 
131 void 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 
147 void SortFilterModel::setFilterRole(const QString &role)
148 {
149  QSortFilterProxyModel::setFilterRole(roleNameToId(role));
150  m_filterRole = role;
151 }
152 
154 {
155  return m_filterRole;
156 }
157 
158 void SortFilterModel::setSortRole(const QString &role)
159 {
160  m_sortRole = role;
161  if (role.isEmpty()) {
163  } else if (sourceModel()) {
164  QSortFilterProxyModel::setSortRole(roleNameToId(role));
165  sort(sortColumn(), sortOrder());
166  }
167 }
168 
170 {
171  return m_sortRole;
172 }
173 
174 void SortFilterModel::setSortOrder(const Qt::SortOrder order)
175 {
176  if (order == sortOrder()) {
177  return;
178  }
179  sort(sortColumn(), order);
180 }
181 
182 void SortFilterModel::setSortColumn(int column)
183 {
184  if (column == sortColumn()) {
185  return;
186  }
187  sort(column, sortOrder());
188  Q_EMIT sortColumnChanged();
189 }
190 
191 QVariantMap 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 
204 int SortFilterModel::mapRowToSource(int row) const
205 {
206  QModelIndex idx = index(row, 0);
207  return mapToSource(idx).row();
208 }
209 
210 int 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 
220 DataModel::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 
236 DataModel::~DataModel()
237 {
238 }
239 
240 static 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 
246 void 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<Plasma::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 
289 void 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<Plasma::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 
365 void 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, QVector<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) {
384  beginResetModel();
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) {
410  endResetModel();
411  } else if (delta > 0) {
412  endInsertRows();
413  } else if (delta < 0) {
414  endRemoveRows();
415  }
416  Q_EMIT dataChanged(createIndex(sourceIndex, 0), createIndex(sourceIndex + qMin(list.length(), oldLength), 0));
417 }
418 
419 QHash<int, QByteArray> DataModel::roleNames() const
420 {
421  return m_roleNames;
422 }
423 
424 void 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) {
432  beginRemoveRows(QModelIndex(), i, i);
433  m_items[QString()].remove(i);
434  endRemoveRows();
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) {
457  endRemoveRows();
458  }
459  }
460  }
461 }
462 
463 QVariant 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, QVector<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 
493 QVariant 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 
502 QModelIndex 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 
511 QModelIndex DataModel::parent(const QModelIndex &child) const
512 {
513  Q_UNUSED(child)
514 
515  return QModelIndex();
516 }
517 
518 int 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 
529 int DataModel::columnCount(const QModelIndex &parent) const
530 {
531  if (parent.isValid()) {
532  return 0;
533  }
534 
535  return 1;
536 }
537 
538 QVariantMap 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 }
void append(const T &value)
QMap::const_iterator constBegin() const const
const T value(const Key &key) const const
QQmlPropertyMap data
All the data fetched by this dataengine.
Definition: datasource.h:122
QQmlEngine * engine() const const
bool isValid() const const
bool contains(const Key &key) const const
DisplayRole
QStringList keys() const const
void beginRemoveRows(const QModelIndex &parent, int first, int last)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
Namespace for everything in libplasma.
Definition: datamodel.cpp:14
int sortColumn
Specify which column should be used for sorting.
Definition: datamodel.h:77
QString fromUtf8(const char *str, int size)
int size() const const
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
Definition: datamodel.cpp:191
QString sortRole
The role of the sourceModel that will be used for sorting.
Definition: datamodel.h:67
Q_EMITQ_EMIT
void setSourceFilter(const QString &key)
Include only sources that matches this regexp in the model.
Definition: datamodel.cpp:343
const T value(const Key &key, const T &defaultValue) const const
int column() const const
T value() const const
QHash::iterator begin()
int length() const const
AscendingOrder
void clear()
void setFilterRole(int role)
virtual void sort(int column, Qt::SortOrder order) override
QByteArray toLatin1() const const
QString escape(const QString &str)
QString filterRegExp
The regular expression for the filter, only items with their filterRole matching filterRegExp will be...
Definition: datamodel.h:42
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
QString filterString
The string for the filter, only items with their filterRole matching filterString will be displayed.
Definition: datamodel.h:47
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
QString filterRole
The role of the sourceModel on which filterRegExp must be applied.
Definition: datamodel.h:62
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void beginInsertRows(const QModelIndex &parent, int first, int last)
bool toBool() const const
QVector< T > toVector() const const
int remove(const Key &key)
bool isEmpty() const const
void setSortRole(int role)
QVariant data(int role) const const
QQmlContext * contextForObject(const QObject *object)
virtual QHash< int, QByteArray > roleNames() const const
QModelIndex createIndex(int row, int column, void *ptr) const const
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
Definition: datamodel.cpp:538
QHash::const_iterator constBegin() const const
QHash::const_iterator constEnd() const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
QMap::const_iterator constEnd() const const
void reserve(int size)
int count
How many items are in this model.
Definition: datamodel.h:183
Orientation
bool isEmpty() const const
const Key key(const T &value) const const
bool isEmpty() const const
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QObject dataSource
The instance of DataSource to construct this model on.
Definition: datamodel.h:164
bool isValid() const const
const Key key(const T &value, const Key &defaultKey) const const
bool strictlyEquals(const QJSValue &other) const const
QString sourceFilter
It's a regular expression.
Definition: datamodel.h:178
void setFilterRegularExpression(const QString &pattern)
int row() const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
bool canConvert(int targetTypeId) const const
QVariant value(const QString &key) const const
void rowsInserted(const QModelIndex &parent, int first, int last)
QJSValue filterCallback
A JavaScript callable that is passed the source model row index as first argument and the value of fi...
Definition: datamodel.h:57
void rowsRemoved(const QModelIndex &parent, int first, int last)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
void setObjectName(const QString &name)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
bool isCallable() 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 setKeyRoleFilter(const QString &key)
Include only items with a key that matches this regexp in the model.
Definition: datamodel.cpp:321
QJSValue call(const QJSValueList &args)
QAbstractItemModel sourceModel
The source model of this sorting proxy model.
Definition: datamodel.h:37
int count(const Key &key) const const
bool contains(const Key &key) const const
QJSValue toScriptValue(const T &value)
QObject * parent() const const
Qt::SortOrder sortOrder
One of Qt.Ascending or Qt.Descending.
Definition: datamodel.h:72
bool isValid() const const
Filter and sort an existing QAbstractItemModel.
Definition: datamodel.h:31
QHash::iterator end()
QString keyRoleFilter
It's a regular expression.
Definition: datamodel.h:170
bool isNull() const const
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Mar 26 2023 04:14:47 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.