File indexing completed on 2025-02-02 05:26:32
0001 /* 0002 SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "datamodel.h" 0008 #include "datasource.h" 0009 0010 #include <QQmlContext> 0011 #include <QQmlEngine> 0012 #include <QTimer> 0013 0014 namespace Plasma5Support 0015 { 0016 SortFilterModel::SortFilterModel(QObject *parent) 0017 : QSortFilterProxyModel(parent) 0018 { 0019 setObjectName(QStringLiteral("SortFilterModel")); 0020 setDynamicSortFilter(true); 0021 connect(this, &QAbstractItemModel::rowsInserted, this, &SortFilterModel::countChanged); 0022 connect(this, &QAbstractItemModel::rowsRemoved, this, &SortFilterModel::countChanged); 0023 connect(this, &QAbstractItemModel::modelReset, this, &SortFilterModel::countChanged); 0024 connect(this, &SortFilterModel::countChanged, this, &SortFilterModel::syncRoleNames); 0025 } 0026 0027 SortFilterModel::~SortFilterModel() 0028 { 0029 } 0030 0031 void SortFilterModel::syncRoleNames() 0032 { 0033 if (!sourceModel()) { 0034 return; 0035 } 0036 0037 m_roleIds.clear(); 0038 const QHash<int, QByteArray> rNames = roleNames(); 0039 m_roleIds.reserve(rNames.count()); 0040 for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { 0041 m_roleIds[QString::fromUtf8(i.value())] = i.key(); 0042 } 0043 0044 setFilterRole(m_filterRole); 0045 setSortRole(m_sortRole); 0046 } 0047 0048 QHash<int, QByteArray> SortFilterModel::roleNames() const 0049 { 0050 if (sourceModel()) { 0051 return sourceModel()->roleNames(); 0052 } 0053 return {}; 0054 } 0055 0056 int SortFilterModel::roleNameToId(const QString &name) const 0057 { 0058 return m_roleIds.value(name, Qt::DisplayRole); 0059 } 0060 0061 void SortFilterModel::setModel(QAbstractItemModel *model) 0062 { 0063 if (model == sourceModel()) { 0064 return; 0065 } 0066 0067 if (sourceModel()) { 0068 disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames); 0069 } 0070 0071 QSortFilterProxyModel::setSourceModel(model); 0072 0073 if (model) { 0074 connect(model, &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames); 0075 syncRoleNames(); 0076 } 0077 0078 Q_EMIT sourceModelChanged(model); 0079 } 0080 0081 bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0082 { 0083 if (m_filterCallback.isCallable()) { 0084 QJSValueList args; 0085 args << QJSValue(source_row); 0086 0087 const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent); 0088 QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); 0089 args << engine->toScriptValue<QVariant>(idx.data(m_roleIds.value(m_filterRole))); 0090 0091 return const_cast<SortFilterModel *>(this)->m_filterCallback.call(args).toBool(); 0092 } 0093 0094 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); 0095 } 0096 0097 void SortFilterModel::setFilterRegExp(const QString &exp) 0098 { 0099 if (exp == filterRegExp()) { 0100 return; 0101 } 0102 QSortFilterProxyModel::setFilterRegularExpression(QRegularExpression(exp, QRegularExpression::CaseInsensitiveOption)); 0103 Q_EMIT filterRegExpChanged(exp); 0104 } 0105 0106 QString SortFilterModel::filterRegExp() const 0107 { 0108 return QSortFilterProxyModel::filterRegularExpression().pattern(); 0109 } 0110 0111 void SortFilterModel::setFilterString(const QString &filterString) 0112 { 0113 if (filterString == m_filterString) { 0114 return; 0115 } 0116 m_filterString = filterString; 0117 QSortFilterProxyModel::setFilterRegularExpression(QRegularExpression{QRegularExpression::escape(filterString), QRegularExpression::CaseInsensitiveOption}); 0118 Q_EMIT filterStringChanged(filterString); 0119 } 0120 0121 QString SortFilterModel::filterString() const 0122 { 0123 return m_filterString; 0124 } 0125 0126 QJSValue SortFilterModel::filterCallback() const 0127 { 0128 return m_filterCallback; 0129 } 0130 0131 void SortFilterModel::setFilterCallback(const QJSValue &callback) 0132 { 0133 if (m_filterCallback.strictlyEquals(callback)) { 0134 return; 0135 } 0136 0137 if (!callback.isNull() && !callback.isCallable()) { 0138 return; 0139 } 0140 0141 m_filterCallback = callback; 0142 invalidateFilter(); 0143 0144 Q_EMIT filterCallbackChanged(callback); 0145 } 0146 0147 void SortFilterModel::setFilterRole(const QString &role) 0148 { 0149 QSortFilterProxyModel::setFilterRole(roleNameToId(role)); 0150 m_filterRole = role; 0151 } 0152 0153 QString SortFilterModel::filterRole() const 0154 { 0155 return m_filterRole; 0156 } 0157 0158 void SortFilterModel::setSortRole(const QString &role) 0159 { 0160 m_sortRole = role; 0161 if (role.isEmpty()) { 0162 sort(-1, Qt::AscendingOrder); 0163 } else if (sourceModel()) { 0164 QSortFilterProxyModel::setSortRole(roleNameToId(role)); 0165 sort(sortColumn(), sortOrder()); 0166 } 0167 } 0168 0169 QString SortFilterModel::sortRole() const 0170 { 0171 return m_sortRole; 0172 } 0173 0174 void SortFilterModel::setSortOrder(const Qt::SortOrder order) 0175 { 0176 if (order == sortOrder()) { 0177 return; 0178 } 0179 sort(sortColumn(), order); 0180 } 0181 0182 void SortFilterModel::setSortColumn(int column) 0183 { 0184 if (column == sortColumn()) { 0185 return; 0186 } 0187 sort(column, sortOrder()); 0188 Q_EMIT sortColumnChanged(); 0189 } 0190 0191 QVariantMap SortFilterModel::get(int row) const 0192 { 0193 QModelIndex idx = index(row, 0); 0194 QVariantMap hash; 0195 0196 const QHash<int, QByteArray> rNames = roleNames(); 0197 for (auto i = rNames.begin(); i != rNames.end(); ++i) { 0198 hash[QString::fromUtf8(i.value())] = data(idx, i.key()); 0199 } 0200 0201 return hash; 0202 } 0203 0204 int SortFilterModel::mapRowToSource(int row) const 0205 { 0206 QModelIndex idx = index(row, 0); 0207 return mapToSource(idx).row(); 0208 } 0209 0210 int SortFilterModel::mapRowFromSource(int row) const 0211 { 0212 if (!sourceModel()) { 0213 qWarning() << "No source model defined!"; 0214 return -1; 0215 } 0216 QModelIndex idx = sourceModel()->index(row, 0); 0217 return mapFromSource(idx).row(); 0218 } 0219 0220 DataModel::DataModel(QObject *parent) 0221 : QAbstractItemModel(parent) 0222 , m_dataSource(nullptr) 0223 , m_maxRoleId(Qt::UserRole + 1) 0224 { 0225 // There is one reserved role name: DataEngineSource 0226 m_roleNames[m_maxRoleId] = QByteArrayLiteral("DataEngineSource"); 0227 m_roleIds[QStringLiteral("DataEngineSource")] = m_maxRoleId; 0228 ++m_maxRoleId; 0229 0230 setObjectName(QStringLiteral("DataModel")); 0231 connect(this, &QAbstractItemModel::rowsInserted, this, &DataModel::countChanged); 0232 connect(this, &QAbstractItemModel::rowsRemoved, this, &DataModel::countChanged); 0233 connect(this, &QAbstractItemModel::modelReset, this, &DataModel::countChanged); 0234 } 0235 0236 DataModel::~DataModel() 0237 { 0238 } 0239 0240 static bool isExactMatch(const QRegularExpression &re, const QString &s) 0241 { 0242 const auto match = re.match(s); 0243 return match.hasMatch() && s.size() == match.capturedLength(); 0244 } 0245 0246 void DataModel::dataUpdated(const QString &sourceName, const QVariantMap &data) 0247 { 0248 if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !isExactMatch(m_sourceFilterRE, sourceName)) { 0249 return; 0250 } 0251 0252 if (m_keyRoleFilter.isEmpty()) { 0253 // an item is represented by a source: keys are roles m_roleLevel == FirstLevel 0254 QVariantList list; 0255 0256 if (!m_dataSource->data()->isEmpty()) { 0257 const auto lst = m_dataSource->data()->keys(); 0258 for (const QString &key : lst) { 0259 if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !isExactMatch(m_sourceFilterRE, key)) { 0260 continue; 0261 } 0262 QVariant value = m_dataSource->data()->value(key); 0263 if (value.isValid() && value.canConvert<Plasma5Support::DataEngine::Data>()) { 0264 Plasma5Support::DataEngine::Data data = value.value<Plasma5Support::DataEngine::Data>(); 0265 data[QStringLiteral("DataEngineSource")] = key; 0266 list.append(data); 0267 } 0268 } 0269 } 0270 setItems(QString(), list); 0271 } else { 0272 // a key that matches the one we want exists and is a list of DataEngine::Data 0273 if (data.contains(m_keyRoleFilter) && data.value(m_keyRoleFilter).canConvert<QVariantList>()) { 0274 setItems(sourceName, data.value(m_keyRoleFilter).value<QVariantList>()); 0275 } else if (m_keyRoleFilterRE.isValid()) { 0276 // try to match the key we want with a regular expression if set 0277 QVariantList list; 0278 QVariantMap::const_iterator i; 0279 for (i = data.constBegin(); i != data.constEnd(); ++i) { 0280 if (isExactMatch(m_keyRoleFilterRE, i.key())) { 0281 list.append(i.value()); 0282 } 0283 } 0284 setItems(sourceName, list); 0285 } 0286 } 0287 } 0288 0289 void DataModel::setDataSource(QObject *object) 0290 { 0291 DataSource *source = qobject_cast<DataSource *>(object); 0292 if (!source) { 0293 qWarning() << "Error: DataSource type expected"; 0294 return; 0295 } 0296 if (m_dataSource == source) { 0297 return; 0298 } 0299 0300 if (m_dataSource) { 0301 disconnect(m_dataSource, nullptr, this, nullptr); 0302 } 0303 0304 m_dataSource = source; 0305 0306 const auto keys = m_dataSource->data()->keys(); 0307 for (const QString &key : keys) { 0308 dataUpdated(key, m_dataSource->data()->value(key).value<Plasma5Support::DataEngine::Data>()); 0309 } 0310 0311 connect(m_dataSource, &DataSource::newData, this, &DataModel::dataUpdated); 0312 connect(m_dataSource, &DataSource::sourceRemoved, this, &DataModel::removeSource); 0313 connect(m_dataSource, &DataSource::sourceDisconnected, this, &DataModel::removeSource); 0314 } 0315 0316 QObject *DataModel::dataSource() const 0317 { 0318 return m_dataSource; 0319 } 0320 0321 void DataModel::setKeyRoleFilter(const QString &key) 0322 { 0323 // the "key role filter" can be used in one of three ways: 0324 // 0325 // 1) empty string -> all data is used, each source is one row in the model 0326 // 2) matches a key in the data exactly -> only that key/value pair is used, and the value is 0327 // treated as a collection where each item in the collection becomes a row in the model 0328 // 3) regular expression -> matches zero or more keys in the data, and each matching key/value 0329 // pair becomes a row in the model 0330 if (m_keyRoleFilter == key) { 0331 return; 0332 } 0333 0334 m_keyRoleFilter = key; 0335 m_keyRoleFilterRE = QRegularExpression(m_keyRoleFilter); 0336 } 0337 0338 QString DataModel::keyRoleFilter() const 0339 { 0340 return m_keyRoleFilter; 0341 } 0342 0343 void DataModel::setSourceFilter(const QString &key) 0344 { 0345 if (m_sourceFilter == key) { 0346 return; 0347 } 0348 0349 m_sourceFilter = key; 0350 m_sourceFilterRE = QRegularExpression(key); 0351 /* 0352 FIXME: if the user changes the source filter, it won't immediately be reflected in the 0353 available data 0354 if (m_sourceFilterRE.isValid()) { 0355 .. iterate through all items and weed out the ones that don't match .. 0356 } 0357 */ 0358 } 0359 0360 QString DataModel::sourceFilter() const 0361 { 0362 return m_sourceFilter; 0363 } 0364 0365 void DataModel::setItems(const QString &sourceName, const QVariantList &list) 0366 { 0367 const int oldLength = m_items.value(sourceName).count(); 0368 const int delta = list.length() - oldLength; 0369 const bool firstRun = m_items.isEmpty(); 0370 0371 // At what row number the first item associated to this source starts 0372 int sourceIndex = 0; 0373 QMap<QString, QList<QVariant>>::const_iterator i; 0374 for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) { 0375 if (i.key() == sourceName) { 0376 break; 0377 } 0378 sourceIndex += i.value().count(); 0379 } 0380 // signal as inserted the rows at the end, all the other rows will signal a dataupdated. 0381 // better than a model reset because doesn't cause deletion and re-creation of every list item on a qml ListView, repeaters etc. 0382 // the first run it gets reset because otherwise setRoleNames gets broken 0383 if (firstRun) { 0384 beginResetModel(); 0385 } else if (delta > 0) { 0386 beginInsertRows(QModelIndex(), sourceIndex + oldLength, sourceIndex + list.length() - 1); 0387 } else if (delta < 0) { 0388 beginRemoveRows(QModelIndex(), sourceIndex + list.length(), sourceIndex + oldLength - 1); 0389 } 0390 // convert to vector, so data() will be O(1) 0391 m_items[sourceName] = list.toVector(); 0392 0393 if (!list.isEmpty()) { 0394 for (const QVariant &item : list) { 0395 const QVariantMap &vh = item.value<QVariantMap>(); 0396 QMapIterator<QString, QVariant> it(vh); 0397 while (it.hasNext()) { 0398 it.next(); 0399 const QString &roleName = it.key(); 0400 if (!m_roleIds.contains(roleName)) { 0401 ++m_maxRoleId; 0402 m_roleNames[m_maxRoleId] = roleName.toLatin1(); 0403 m_roleIds[roleName] = m_maxRoleId; 0404 } 0405 } 0406 } 0407 } 0408 0409 if (firstRun) { 0410 endResetModel(); 0411 } else if (delta > 0) { 0412 endInsertRows(); 0413 } else if (delta < 0) { 0414 endRemoveRows(); 0415 } 0416 Q_EMIT dataChanged(createIndex(sourceIndex, 0), createIndex(sourceIndex + qMin(list.length(), oldLength), 0)); 0417 } 0418 0419 QHash<int, QByteArray> DataModel::roleNames() const 0420 { 0421 return m_roleNames; 0422 } 0423 0424 void DataModel::removeSource(const QString &sourceName) 0425 { 0426 // FIXME: find a way to remove only the proper things also in the case where sources are items 0427 0428 if (m_keyRoleFilter.isEmpty()) { 0429 // source name in the map, linear scan 0430 for (int i = 0; i < m_items.value(QString()).count(); ++i) { 0431 if (m_items.value(QString())[i].value<QVariantMap>().value(QStringLiteral("DataEngineSource")) == sourceName) { 0432 beginRemoveRows(QModelIndex(), i, i); 0433 m_items[QString()].remove(i); 0434 endRemoveRows(); 0435 break; 0436 } 0437 } 0438 } else { 0439 if (m_items.contains(sourceName)) { 0440 // At what row number the first item associated to this source starts 0441 int sourceIndex = 0; 0442 for (auto i = m_items.constBegin(); i != m_items.constEnd(); ++i) { 0443 if (i.key() == sourceName) { 0444 break; 0445 } 0446 sourceIndex += i.value().count(); 0447 } 0448 0449 // source name as key of the map 0450 0451 int count = m_items.value(sourceName).count(); 0452 if (count > 0) { 0453 beginRemoveRows(QModelIndex(), sourceIndex, sourceIndex + count - 1); 0454 } 0455 m_items.remove(sourceName); 0456 if (count > 0) { 0457 endRemoveRows(); 0458 } 0459 } 0460 } 0461 } 0462 0463 QVariant DataModel::data(const QModelIndex &index, int role) const 0464 { 0465 if (!index.isValid() || index.column() > 0 || index.row() < 0 || index.row() >= countItems()) { 0466 return QVariant(); 0467 } 0468 0469 int count = 0; 0470 int actualRow = 0; 0471 QString source; 0472 QMap<QString, QList<QVariant>>::const_iterator i; 0473 for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) { 0474 const int oldCount = count; 0475 count += i.value().count(); 0476 0477 if (index.row() < count) { 0478 source = i.key(); 0479 actualRow = index.row() - oldCount; 0480 break; 0481 } 0482 } 0483 0484 // is it the reserved role: DataEngineSource ? 0485 // also, if each source is an item DataEngineSource is a role between all the others, otherwise we know it from the role variable 0486 if (!m_keyRoleFilter.isEmpty() && m_roleNames.value(role) == "DataEngineSource") { 0487 return source; 0488 } else { 0489 return m_items.value(source).value(actualRow).value<QVariantMap>().value(QString::fromUtf8(m_roleNames.value(role))); 0490 } 0491 } 0492 0493 QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const 0494 { 0495 Q_UNUSED(section) 0496 Q_UNUSED(orientation) 0497 Q_UNUSED(role) 0498 0499 return QVariant(); 0500 } 0501 0502 QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const 0503 { 0504 if (parent.isValid() || column > 0 || row < 0 || row >= countItems()) { 0505 return QModelIndex(); 0506 } 0507 0508 return createIndex(row, column); 0509 } 0510 0511 QModelIndex DataModel::parent(const QModelIndex &child) const 0512 { 0513 Q_UNUSED(child) 0514 0515 return QModelIndex(); 0516 } 0517 0518 int DataModel::rowCount(const QModelIndex &parent) const 0519 { 0520 // this is not a tree 0521 // TODO: make it possible some day? 0522 if (parent.isValid()) { 0523 return 0; 0524 } 0525 0526 return countItems(); 0527 } 0528 0529 int DataModel::columnCount(const QModelIndex &parent) const 0530 { 0531 if (parent.isValid()) { 0532 return 0; 0533 } 0534 0535 return 1; 0536 } 0537 0538 QVariantMap DataModel::get(int row) const 0539 { 0540 QModelIndex idx = index(row, 0); 0541 QVariantMap map; 0542 0543 const QHash<int, QByteArray> rNames = roleNames(); 0544 for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { 0545 map[QString::fromUtf8(i.value())] = data(idx, i.key()); 0546 } 0547 0548 return map; 0549 } 0550 0551 } 0552 0553 #include "moc_datamodel.cpp"