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"