Plasma

datamodel.cpp
1 /*
2  * Copyright 2010 by Marco Martin <[email protected]>
3 
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Library General Public License as
6  * published by the Free Software Foundation; either version 2, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "datamodel.h"
21 #include "datasource.h"
22 
23 #include <QQmlContext>
24 #include <QQmlEngine>
25 #include <QTimer>
26 
27 namespace Plasma
28 {
29 
30 SortFilterModel::SortFilterModel(QObject *parent)
31  : QSortFilterProxyModel(parent)
32 {
33  setObjectName(QStringLiteral("SortFilterModel"));
34  setDynamicSortFilter(true);
35  connect(this, &QAbstractItemModel::rowsInserted,
36  this, &SortFilterModel::countChanged);
37  connect(this, &QAbstractItemModel::rowsRemoved,
38  this, &SortFilterModel::countChanged);
39  connect(this, &QAbstractItemModel::modelReset,
40  this, &SortFilterModel::countChanged);
41  connect(this, &SortFilterModel::countChanged, this, &SortFilterModel::syncRoleNames);
42 }
43 
44 SortFilterModel::~SortFilterModel()
45 {
46 }
47 
48 void SortFilterModel::syncRoleNames()
49 {
50  if (!sourceModel()) {
51  return;
52  }
53 
54  m_roleIds.clear();
55  const QHash<int, QByteArray> rNames = roleNames();
56  m_roleIds.reserve(rNames.count());
57  for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) {
58  m_roleIds[QString::fromUtf8(i.value())] = i.key();
59  }
60 
61  setFilterRole(m_filterRole);
62  setSortRole(m_sortRole);
63 }
64 
65 QHash<int,QByteArray> SortFilterModel::roleNames() const
66 {
67  if (sourceModel()) {
68  return sourceModel()->roleNames();
69  }
70  return {};
71 }
72 
73 int SortFilterModel::roleNameToId(const QString &name) const
74 {
75  return m_roleIds.value(name, Qt::DisplayRole);
76 }
77 
78 void SortFilterModel::setModel(QAbstractItemModel *model)
79 {
80  if (model == sourceModel()) {
81  return;
82  }
83 
84  if (sourceModel()) {
85  disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames);
86  }
87 
89 
90  if (model) {
91  connect(model, &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames);
92  syncRoleNames();
93  }
94 
95  emit sourceModelChanged(model);
96 }
97 
98 bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
99 {
100  if (m_filterCallback.isCallable()) {
101  QJSValueList args;
102  args << QJSValue(source_row);
103 
104  const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
106  args << engine->toScriptValue<QVariant>(idx.data(m_roleIds.value(m_filterRole)));
107 
108  return const_cast<SortFilterModel *>(this)->m_filterCallback.call(args).toBool();
109  }
110 
111  return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
112 }
113 
114 void SortFilterModel::setFilterRegExp(const QString &exp)
115 {
116  if (exp == filterRegExp()) {
117  return;
118  }
120  Q_EMIT filterRegExpChanged(exp);
121 }
122 
123 QString SortFilterModel::filterRegExp() const
124 {
126 }
127 
128 void SortFilterModel::setFilterString(const QString &filterString)
129 {
130  if (filterString == m_filterString) {
131  return;
132  }
133  m_filterString = filterString;
135  Q_EMIT filterStringChanged(filterString);
136 }
137 
138 QString SortFilterModel::filterString() const
139 {
140  return m_filterString;
141 }
142 
143 QJSValue SortFilterModel::filterCallback() const
144 {
145  return m_filterCallback;
146 }
147 
148 void SortFilterModel::setFilterCallback(const QJSValue& callback)
149 {
150  if (m_filterCallback.strictlyEquals(callback)) {
151  return;
152  }
153 
154  if (!callback.isNull() && !callback.isCallable()) {
155  return;
156  }
157 
158  m_filterCallback = callback;
160 
161  Q_EMIT filterCallbackChanged(callback);
162 }
163 
164 void SortFilterModel::setFilterRole(const QString &role)
165 {
166  QSortFilterProxyModel::setFilterRole(roleNameToId(role));
167  m_filterRole = role;
168 }
169 
170 QString SortFilterModel::filterRole() const
171 {
172  return m_filterRole;
173 }
174 
175 void SortFilterModel::setSortRole(const QString &role)
176 {
177  m_sortRole = role;
178  if (role.isEmpty()) {
180  } else if (sourceModel()) {
181  QSortFilterProxyModel::setSortRole(roleNameToId(role));
182  sort(sortColumn(), sortOrder());
183  }
184 }
185 
186 QString SortFilterModel::sortRole() const
187 {
188  return m_sortRole;
189 }
190 
191 void SortFilterModel::setSortOrder(const Qt::SortOrder order)
192 {
193  if (order == sortOrder()) {
194  return;
195  }
196  sort(sortColumn(), order);
197 }
198 
199 void SortFilterModel::setSortColumn(int column)
200 {
201  if (column == sortColumn()) {
202  return;
203  }
204  sort(column, sortOrder());
205  emit sortColumnChanged();
206 }
207 
208 QVariantMap SortFilterModel::get(int row) const
209 {
210  QModelIndex idx = index(row, 0);
211  QVariantMap hash;
212 
213  const QHash<int, QByteArray> rNames = roleNames();
214  for (auto i = rNames.begin(); i != rNames.end(); ++i) {
215  hash[QString::fromUtf8(i.value())] = data(idx, i.key());
216  }
217 
218  return hash;
219 }
220 
221 int SortFilterModel::mapRowToSource(int row) const
222 {
223  QModelIndex idx = index(row, 0);
224  return mapToSource(idx).row();
225 }
226 
227 int SortFilterModel::mapRowFromSource(int row) const
228 {
229  if (!sourceModel()) {
230  qWarning() << "No source model defined!";
231  return -1;
232  }
233  QModelIndex idx = sourceModel()->index(row, 0);
234  return mapFromSource(idx).row();
235 }
236 
237 DataModel::DataModel(QObject *parent)
238  : QAbstractItemModel(parent),
239  m_dataSource(nullptr),
240  m_maxRoleId(Qt::UserRole + 1)
241 {
242  //There is one reserved role name: DataEngineSource
243  m_roleNames[m_maxRoleId] = QByteArrayLiteral("DataEngineSource");
244  m_roleIds[QStringLiteral("DataEngineSource")] = m_maxRoleId;
245  ++m_maxRoleId;
246 
247  setObjectName(QStringLiteral("DataModel"));
249  this, &DataModel::countChanged);
251  this, &DataModel::countChanged);
253  this, &DataModel::countChanged);
254 }
255 
256 DataModel::~DataModel()
257 {
258 }
259 
260 void DataModel::dataUpdated(const QString &sourceName, const QVariantMap &data)
261 {
262  if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(sourceName)) {
263  return;
264  }
265 
266  if (m_keyRoleFilter.isEmpty()) {
267  //an item is represented by a source: keys are roles m_roleLevel == FirstLevel
268  QVariantList list;
269 
270  if (!m_dataSource->data()->isEmpty()) {
271  const auto lst = m_dataSource->data()->keys();
272  for (const QString &key : lst) {
273  if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(key)) {
274  continue;
275  }
276  QVariant value = m_dataSource->data()->value(key);
277  if (value.isValid() && value.canConvert<Plasma::DataEngine::Data>()) {
279  data[QStringLiteral("DataEngineSource")] = key;
280  list.append(data);
281  }
282  }
283  }
284  setItems(QString(), list);
285  } else {
286  //a key that matches the one we want exists and is a list of DataEngine::Data
287  if (data.contains(m_keyRoleFilter) &&
288  data.value(m_keyRoleFilter).canConvert<QVariantList>()) {
289  setItems(sourceName, data.value(m_keyRoleFilter).value<QVariantList>());
290  } else if (m_keyRoleFilterRE.isValid()) {
291  //try to match the key we want with a regular expression if set
292  QVariantList list;
293  QVariantMap::const_iterator i;
294  for (i = data.constBegin(); i != data.constEnd(); ++i) {
295  if (m_keyRoleFilterRE.exactMatch(i.key())) {
296  list.append(i.value());
297  }
298  }
299  setItems(sourceName, list);
300  }
301  }
302 }
303 
304 void DataModel::setDataSource(QObject *object)
305 {
306  DataSource *source = qobject_cast<DataSource *>(object);
307  if (!source) {
308  qWarning() << "Error: DataSource type expected";
309  return;
310  }
311  if (m_dataSource == source) {
312  return;
313  }
314 
315  if (m_dataSource) {
316  disconnect(m_dataSource, nullptr, this, nullptr);
317  }
318 
319  m_dataSource = source;
320 
321  const auto keys = m_dataSource->data()->keys();
322  for (const QString &key : keys) {
323  dataUpdated(key, m_dataSource->data()->value(key).value<Plasma::DataEngine::Data>());
324  }
325 
326  connect(m_dataSource, &DataSource::newData,
327  this, &DataModel::dataUpdated);
328  connect(m_dataSource, &DataSource::sourceRemoved,
329  this, &DataModel::removeSource);
330  connect(m_dataSource, &DataSource::sourceDisconnected,
331  this, &DataModel::removeSource);
332 }
333 
334 QObject *DataModel::dataSource() const
335 {
336  return m_dataSource;
337 }
338 
340 {
341  // the "key role filter" can be used in one of three ways:
342  //
343  // 1) empty string -> all data is used, each source is one row in the model
344  // 2) matches a key in the data exactly -> only that key/value pair is used, and the value is
345  // treated as a collection where each item in the collection becomes a row in the model
346  // 3) regular expression -> matches zero or more keys in the data, and each matching key/value
347  // pair becomes a row in the model
348  if (m_keyRoleFilter == key) {
349  return;
350  }
351 
352  m_keyRoleFilter = key;
353  m_keyRoleFilterRE = QRegExp(m_keyRoleFilter);
354 }
355 
356 QString DataModel::keyRoleFilter() const
357 {
358  return m_keyRoleFilter;
359 }
360 
362 {
363  if (m_sourceFilter == key) {
364  return;
365  }
366 
367  m_sourceFilter = key;
368  m_sourceFilterRE = QRegExp(key);
369  /*
370  FIXME: if the user changes the source filter, it won't immediately be reflected in the
371  available data
372  if (m_sourceFilterRE.isValid()) {
373  .. iterate through all items and weed out the ones that don't match ..
374  }
375  */
376 }
377 
378 QString DataModel::sourceFilter() const
379 {
380  return m_sourceFilter;
381 }
382 
383 void DataModel::setItems(const QString &sourceName, const QVariantList &list)
384 {
385  const int oldLength = m_items.value(sourceName).count();
386  const int delta = list.length() - oldLength;
387  const bool firstRun = m_items.isEmpty();
388 
389  //At what row number the first item associated to this source starts
390  int sourceIndex = 0;
391  QMap<QString, QVector<QVariant> >::const_iterator i;
392  for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
393  if (i.key() == sourceName) {
394  break;
395  }
396  sourceIndex += i.value().count();
397  }
398  //signal as inserted the rows at the end, all the other rows will signal a dataupdated.
399  //better than a model reset because doesn't cause deletion and re-creation of every list item on a qml ListView, repeaters etc.
400  //the first run it gets reset because otherwise setRoleNames gets broken
401  if (firstRun) {
402  beginResetModel();
403  } else if (delta > 0) {
404  beginInsertRows(QModelIndex(), sourceIndex + oldLength, sourceIndex + list.length() - 1);
405  } else if (delta < 0) {
406  beginRemoveRows(QModelIndex(), sourceIndex + list.length(), sourceIndex + oldLength - 1);
407  }
408  //convert to vector, so data() will be O(1)
409  m_items[sourceName] = list.toVector();
410 
411  if (!list.isEmpty()) {
412  if (list.first().canConvert<QVariantMap>()) {
413  for (const QVariant &item : list) {
414  const QVariantMap &vh = item.value<QVariantMap>();
416  while (it.hasNext()) {
417  it.next();
418  const QString &roleName = it.key();
419  if (!m_roleIds.contains(roleName)) {
420  ++m_maxRoleId;
421  m_roleNames[m_maxRoleId] = roleName.toLatin1();
422  m_roleIds[roleName] = m_maxRoleId;
423  }
424  }
425  }
426  } else {
427  for (const QVariant &item : list) {
428  const QVariantMap &vh = item.value<QVariantMap>();
430  while (it.hasNext()) {
431  it.next();
432  const QString &roleName = it.key();
433  if (!m_roleIds.contains(roleName)) {
434  ++m_maxRoleId;
435  m_roleNames[m_maxRoleId] = roleName.toLatin1();
436  m_roleIds[roleName] = m_maxRoleId;
437  }
438  }
439  }
440  }
441  }
442 
443  if (firstRun) {
444  endResetModel();
445  } else if (delta > 0) {
446  endInsertRows();
447  } else if (delta < 0) {
448  endRemoveRows();
449  }
450  emit dataChanged(createIndex(sourceIndex, 0),
451  createIndex(sourceIndex + qMin(list.length(), oldLength), 0));
452 }
453 
454 QHash<int, QByteArray> DataModel::roleNames() const
455 {
456  return m_roleNames;
457 }
458 
459 void DataModel::removeSource(const QString &sourceName)
460 {
461  //FIXME: find a way to remove only the proper things also in the case where sources are items
462 
463  if (m_keyRoleFilter.isEmpty()) {
464  //source name in the map, linear scan
465  for (int i = 0; i < m_items.value(QString()).count(); ++i) {
466  if (m_items.value(QString())[i].value<QVariantMap>().value(QStringLiteral("DataEngineSource")) == sourceName) {
467  beginRemoveRows(QModelIndex(), i, i);
468  m_items[QString()].remove(i);
469  endRemoveRows();
470  break;
471  }
472  }
473  } else {
474  if (m_items.contains(sourceName)) {
475  //At what row number the first item associated to this source starts
476  int sourceIndex = 0;
477  for (auto i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
478  if (i.key() == sourceName) {
479  break;
480  }
481  sourceIndex += i.value().count();
482  }
483 
484  //source name as key of the map
485 
486  int count = m_items.value(sourceName).count();
487  if (count > 0) {
488  beginRemoveRows(QModelIndex(), sourceIndex, sourceIndex + count - 1);
489  }
490  m_items.remove(sourceName);
491  if (count > 0) {
492  endRemoveRows();
493  }
494  }
495  }
496 }
497 
498 QVariant DataModel::data(const QModelIndex &index, int role) const
499 {
500  if (!index.isValid() || index.column() > 0 ||
501  index.row() < 0 || index.row() >= countItems()) {
502  return QVariant();
503  }
504 
505  int count = 0;
506  int actualRow = 0;
507  QString source;
508  QMap<QString, QVector<QVariant> >::const_iterator i;
509  for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) {
510  const int oldCount = count;
511  count += i.value().count();
512 
513  if (index.row() < count) {
514  source = i.key();
515  actualRow = index.row() - oldCount;
516  break;
517  }
518  }
519 
520  //is it the reserved role: DataEngineSource ?
521  //also, if each source is an item DataEngineSource is a role between all the others, otherwise we know it from the role variable
522  if (!m_keyRoleFilter.isEmpty() && m_roleNames.value(role) == "DataEngineSource") {
523  return source;
524  } else {
525  return m_items.value(source).value(actualRow).value<QVariantMap>().value(QString::fromUtf8(m_roleNames.value(role)));
526  }
527 }
528 
529 QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const
530 {
531  Q_UNUSED(section)
532  Q_UNUSED(orientation)
533  Q_UNUSED(role)
534 
535  return QVariant();
536 }
537 
538 QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const
539 {
540  if (parent.isValid() || column > 0 || row < 0 || row >= countItems()) {
541  return QModelIndex();
542  }
543 
544  return createIndex(row, column);
545 }
546 
547 QModelIndex DataModel::parent(const QModelIndex &child) const
548 {
549  Q_UNUSED(child)
550 
551  return QModelIndex();
552 }
553 
554 int DataModel::rowCount(const QModelIndex &parent) const
555 {
556  //this is not a tree
557  //TODO: make it possible some day?
558  if (parent.isValid()) {
559  return 0;
560  }
561 
562  return countItems();
563 }
564 
565 int DataModel::columnCount(const QModelIndex &parent) const
566 {
567  if (parent.isValid()) {
568  return 0;
569  }
570 
571  return 1;
572 }
573 
574 QVariantMap DataModel::get(int row) const
575 {
576  QModelIndex idx = index(row, 0);
577  QVariantMap map;
578 
579  const QHash<int, QByteArray> rNames = roleNames();
580  for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) {
581  map[QString::fromUtf8(i.value())] = data(idx, i.key());
582  }
583 
584  return map;
585 }
586 
587 }
588 
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:42
void setSourceFilter(const QString &key)
Include only sources that matches this regexp in the model.
Definition: datamodel.cpp:361
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:27
void setKeyRoleFilter(const QString &key)
Include only items with a key that matches this regexp in the model.
Definition: datamodel.cpp:339
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:208
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:574
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:128
Provides data from a range of plugins.
Definition: datasource.h:45
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-2020 The KDE developers.
Generated on Wed Aug 12 2020 22:41:25 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.