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 
106 QString SortFilterModel::filterRegExp() const
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 
121 QString SortFilterModel::filterString() const
122 {
123  return m_filterString;
124 }
125 
126 QJSValue SortFilterModel::filterCallback() const
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 
153 QString SortFilterModel::filterRole() const
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 
169 QString SortFilterModel::sortRole() const
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 void DataModel::dataUpdated(const QString &sourceName, const QVariantMap &data)
241 {
242  if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(sourceName)) {
243  return;
244  }
245 
246  if (m_keyRoleFilter.isEmpty()) {
247  // an item is represented by a source: keys are roles m_roleLevel == FirstLevel
248  QVariantList list;
249 
250  if (!m_dataSource->data()->isEmpty()) {
251  const auto lst = m_dataSource->data()->keys();
252  for (const QString &key : lst) {
253  if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(key)) {
254  continue;
255  }
256  QVariant value = m_dataSource->data()->value(key);
257  if (value.isValid() && value.canConvert<Plasma::DataEngine::Data>()) {
259  data[QStringLiteral("DataEngineSource")] = key;
260  list.append(data);
261  }
262  }
263  }
264  setItems(QString(), list);
265  } else {
266  // a key that matches the one we want exists and is a list of DataEngine::Data
267  if (data.contains(m_keyRoleFilter) && data.value(m_keyRoleFilter).canConvert<QVariantList>()) {
268  setItems(sourceName, data.value(m_keyRoleFilter).value<QVariantList>());
269  } else if (m_keyRoleFilterRE.isValid()) {
270  // try to match the key we want with a regular expression if set
271  QVariantList list;
272  QVariantMap::const_iterator i;
273  for (i = data.constBegin(); i != data.constEnd(); ++i) {
274  if (m_keyRoleFilterRE.exactMatch(i.key())) {
275  list.append(i.value());
276  }
277  }
278  setItems(sourceName, list);
279  }
280  }
281 }
282 
283 void DataModel::setDataSource(QObject *object)
284 {
285  DataSource *source = qobject_cast<DataSource *>(object);
286  if (!source) {
287  qWarning() << "Error: DataSource type expected";
288  return;
289  }
290  if (m_dataSource == source) {
291  return;
292  }
293 
294  if (m_dataSource) {
295  disconnect(m_dataSource, nullptr, this, nullptr);
296  }
297 
298  m_dataSource = source;
299 
300  const auto keys = m_dataSource->data()->keys();
301  for (const QString &key : keys) {
302  dataUpdated(key, m_dataSource->data()->value(key).value<Plasma::DataEngine::Data>());
303  }
304 
305  connect(m_dataSource, &DataSource::newData, this, &DataModel::dataUpdated);
306  connect(m_dataSource, &DataSource::sourceRemoved, this, &DataModel::removeSource);
307  connect(m_dataSource, &DataSource::sourceDisconnected, this, &DataModel::removeSource);
308 }
309 
310 QObject *DataModel::dataSource() const
311 {
312  return m_dataSource;
313 }
314 
316 {
317  // the "key role filter" can be used in one of three ways:
318  //
319  // 1) empty string -> all data is used, each source is one row in the model
320  // 2) matches a key in the data exactly -> only that key/value pair is used, and the value is
321  // treated as a collection where each item in the collection becomes a row in the model
322  // 3) regular expression -> matches zero or more keys in the data, and each matching key/value
323  // pair becomes a row in the model
324  if (m_keyRoleFilter == key) {
325  return;
326  }
327 
328  m_keyRoleFilter = key;
329  m_keyRoleFilterRE = QRegExp(m_keyRoleFilter);
330 }
331 
332 QString DataModel::keyRoleFilter() const
333 {
334  return m_keyRoleFilter;
335 }
336 
338 {
339  if (m_sourceFilter == key) {
340  return;
341  }
342 
343  m_sourceFilter = key;
344  m_sourceFilterRE = QRegExp(key);
345  /*
346  FIXME: if the user changes the source filter, it won't immediately be reflected in the
347  available data
348  if (m_sourceFilterRE.isValid()) {
349  .. iterate through all items and weed out the ones that don't match ..
350  }
351  */
352 }
353 
354 QString DataModel::sourceFilter() const
355 {
356  return m_sourceFilter;
357 }
358 
359 void DataModel::setItems(const QString &sourceName, const QVariantList &list)
360 {
361  const int oldLength = m_items.value(sourceName).count();
362  const int delta = list.length() - oldLength;
363  const bool firstRun = m_items.isEmpty();
364 
365  // At what row number the first item associated to this source starts
366  int sourceIndex = 0;
367  QMap<QString, QVector<QVariant>>::const_iterator i;
368  for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
369  if (i.key() == sourceName) {
370  break;
371  }
372  sourceIndex += i.value().count();
373  }
374  // signal as inserted the rows at the end, all the other rows will signal a dataupdated.
375  // better than a model reset because doesn't cause deletion and re-creation of every list item on a qml ListView, repeaters etc.
376  // the first run it gets reset because otherwise setRoleNames gets broken
377  if (firstRun) {
378  beginResetModel();
379  } else if (delta > 0) {
380  beginInsertRows(QModelIndex(), sourceIndex + oldLength, sourceIndex + list.length() - 1);
381  } else if (delta < 0) {
382  beginRemoveRows(QModelIndex(), sourceIndex + list.length(), sourceIndex + oldLength - 1);
383  }
384  // convert to vector, so data() will be O(1)
385  m_items[sourceName] = list.toVector();
386 
387  if (!list.isEmpty()) {
388  if (list.first().canConvert<QVariantMap>()) {
389  for (const QVariant &item : list) {
390  const QVariantMap &vh = item.value<QVariantMap>();
392  while (it.hasNext()) {
393  it.next();
394  const QString &roleName = it.key();
395  if (!m_roleIds.contains(roleName)) {
396  ++m_maxRoleId;
397  m_roleNames[m_maxRoleId] = roleName.toLatin1();
398  m_roleIds[roleName] = m_maxRoleId;
399  }
400  }
401  }
402  } else {
403  for (const QVariant &item : list) {
404  const QVariantMap &vh = item.value<QVariantMap>();
406  while (it.hasNext()) {
407  it.next();
408  const QString &roleName = it.key();
409  if (!m_roleIds.contains(roleName)) {
410  ++m_maxRoleId;
411  m_roleNames[m_maxRoleId] = roleName.toLatin1();
412  m_roleIds[roleName] = m_maxRoleId;
413  }
414  }
415  }
416  }
417  }
418 
419  if (firstRun) {
420  endResetModel();
421  } else if (delta > 0) {
422  endInsertRows();
423  } else if (delta < 0) {
424  endRemoveRows();
425  }
426  Q_EMIT dataChanged(createIndex(sourceIndex, 0), createIndex(sourceIndex + qMin(list.length(), oldLength), 0));
427 }
428 
429 QHash<int, QByteArray> DataModel::roleNames() const
430 {
431  return m_roleNames;
432 }
433 
434 void DataModel::removeSource(const QString &sourceName)
435 {
436  // FIXME: find a way to remove only the proper things also in the case where sources are items
437 
438  if (m_keyRoleFilter.isEmpty()) {
439  // source name in the map, linear scan
440  for (int i = 0; i < m_items.value(QString()).count(); ++i) {
441  if (m_items.value(QString())[i].value<QVariantMap>().value(QStringLiteral("DataEngineSource")) == sourceName) {
442  beginRemoveRows(QModelIndex(), i, i);
443  m_items[QString()].remove(i);
444  endRemoveRows();
445  break;
446  }
447  }
448  } else {
449  if (m_items.contains(sourceName)) {
450  // At what row number the first item associated to this source starts
451  int sourceIndex = 0;
452  for (auto i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
453  if (i.key() == sourceName) {
454  break;
455  }
456  sourceIndex += i.value().count();
457  }
458 
459  // source name as key of the map
460 
461  int count = m_items.value(sourceName).count();
462  if (count > 0) {
463  beginRemoveRows(QModelIndex(), sourceIndex, sourceIndex + count - 1);
464  }
465  m_items.remove(sourceName);
466  if (count > 0) {
467  endRemoveRows();
468  }
469  }
470  }
471 }
472 
473 QVariant DataModel::data(const QModelIndex &index, int role) const
474 {
475  if (!index.isValid() || index.column() > 0 || index.row() < 0 || index.row() >= countItems()) {
476  return QVariant();
477  }
478 
479  int count = 0;
480  int actualRow = 0;
481  QString source;
482  QMap<QString, QVector<QVariant>>::const_iterator i;
483  for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
484  const int oldCount = count;
485  count += i.value().count();
486 
487  if (index.row() < count) {
488  source = i.key();
489  actualRow = index.row() - oldCount;
490  break;
491  }
492  }
493 
494  // is it the reserved role: DataEngineSource ?
495  // also, if each source is an item DataEngineSource is a role between all the others, otherwise we know it from the role variable
496  if (!m_keyRoleFilter.isEmpty() && m_roleNames.value(role) == "DataEngineSource") {
497  return source;
498  } else {
499  return m_items.value(source).value(actualRow).value<QVariantMap>().value(QString::fromUtf8(m_roleNames.value(role)));
500  }
501 }
502 
503 QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const
504 {
505  Q_UNUSED(section)
506  Q_UNUSED(orientation)
507  Q_UNUSED(role)
508 
509  return QVariant();
510 }
511 
512 QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const
513 {
514  if (parent.isValid() || column > 0 || row < 0 || row >= countItems()) {
515  return QModelIndex();
516  }
517 
518  return createIndex(row, column);
519 }
520 
521 QModelIndex DataModel::parent(const QModelIndex &child) const
522 {
523  Q_UNUSED(child)
524 
525  return QModelIndex();
526 }
527 
528 int DataModel::rowCount(const QModelIndex &parent) const
529 {
530  // this is not a tree
531  // TODO: make it possible some day?
532  if (parent.isValid()) {
533  return 0;
534  }
535 
536  return countItems();
537 }
538 
539 int DataModel::columnCount(const QModelIndex &parent) const
540 {
541  if (parent.isValid()) {
542  return 0;
543  }
544 
545  return 1;
546 }
547 
548 QVariantMap DataModel::get(int row) const
549 {
550  QModelIndex idx = index(row, 0);
551  QVariantMap map;
552 
553  const QHash<int, QByteArray> rNames = roleNames();
554  for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) {
555  map[QString::fromUtf8(i.value())] = data(idx, i.key());
556  }
557 
558  return map;
559 }
560 
561 }
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
bool canConvert(int targetTypeId) const const
void setSortRole(int role)
QStringList keys() const const
Filter and sort an existing QAbstractItemModel.
Definition: datamodel.h:30
void setSourceFilter(const QString &key)
Include only sources that matches this regexp in the model.
Definition: datamodel.cpp:337
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const =0
const Key key(const T &value) const const
virtual void sort(int column, Qt::SortOrder order) override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
virtual QHash< int, QByteArray > roleNames() const const
QMap::const_iterator constBegin() const const
void reserve(int size)
T value() const const
QJSValue call(const QJSValueList &args)
int count(const Key &key) const const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
Namespace for everything in libplasma.
Definition: datamodel.cpp:14
void setKeyRoleFilter(const QString &key)
Include only items with a key that matches this regexp in the model.
Definition: datamodel.cpp:315
bool isValid() const const
void setFilterRole(int role)
void append(const T &value)
QString fromUtf8(const char *str, int size)
QHash::const_iterator constEnd() const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
void setFilterRegExp(const QString &pattern)
CaseInsensitive
bool toBool() const const
bool strictlyEquals(const QJSValue &other) const const
void setObjectName(const QString &name)
DisplayRole
bool isEmpty() const const
QMap::const_iterator constEnd() const const
void beginRemoveRows(const QModelIndex &parent, int first, int last)
int row() const const
QHash::iterator begin()
bool isNull() const const
void rowsRemoved(const QModelIndex &parent, int first, int last)
QQmlEngine * engine() const const
bool isCallable() const const
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
Definition: datamodel.cpp:191
QModelIndex createIndex(int row, int column, void *ptr) const const
void clear()
const T value(const Key &key) const const
void beginInsertRows(const QModelIndex &parent, int first, int last)
const Key key(const T &value, const Key &defaultKey) const const
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
QHash::const_iterator constBegin() const const
QAbstractItemModel * sourceModel() const const
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
QByteArray toLatin1() const const
QJSValue toScriptValue(const T &value)
Qt::SortOrder sortOrder() const const
QVariant data(int role) const const
AscendingOrder
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
int count() const const
QString pattern() const const
QQmlContext * contextForObject(const QObject *object)
int column() const const
void setFilterFixedString(const QString &pattern)
bool isValid() const const
Q_INVOKABLE QVariantMap get(int i) const
Returns the item at index in the list model.
Definition: datamodel.cpp:548
bool contains(const Key &key) const const
QAbstractItemModel(QObject *parent)
Orientation
QHash::iterator end()
QQmlPropertyMap data
All the data fetched by this dataengine.
Definition: datasource.h:117
Provides data from a range of plugins.
Definition: datasource.h:34
void rowsInserted(const QModelIndex &parent, int first, int last)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
int sortColumn() const const
int filterKeyColumn() const const
virtual QVariant data(const QModelIndex &index, int role) const const override
Q_EMITQ_EMIT
QRegExp filterRegExp() const const
const T value(const Key &key, const T &defaultValue) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Jun 20 2021 22:41:15 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.