| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 Ford Motor Company |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtRemoteObjects module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #ifndef QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H |
| #define QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H |
| |
| // |
| // W A R N I N G |
| // ------------- |
| // |
| // This file is not part of the Qt API. It exists purely as an |
| // implementation detail. This header file may change from version to |
| // version without notice, or even be removed. |
| // |
| // We mean it. |
| // |
| |
| #include "qremoteobjectabstractitemmodeltypes.h" |
| #include "qremoteobjectabstractitemmodelreplica.h" |
| #include "qremoteobjectreplica.h" |
| #include "qremoteobjectpendingcall.h" |
| #include <list> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace { |
| const int DefaultNodesCacheSize = 1000; |
| } |
| |
| struct CacheEntry |
| { |
| QHash<int, QVariant> data; |
| Qt::ItemFlags flags; |
| |
| explicit CacheEntry() |
| : flags(Qt::NoItemFlags) |
| {} |
| }; |
| |
| typedef QVector<CacheEntry> CachedRowEntry; |
| |
| template <class Key, class Value> |
| struct LRUCache |
| { |
| typedef std::pair<Key, Value*> Pair; |
| std::list<Pair> cachedItems; |
| typedef typename std::list<Pair>::iterator CacheIterator; |
| std::unordered_map<Key, CacheIterator> cachedItemsMap; |
| size_t cacheSize; |
| |
| explicit LRUCache() |
| { |
| bool ok; |
| cacheSize = qEnvironmentVariableIntValue("QTRO_NODES_CACHE_SIZE" , &ok); |
| if (!ok) |
| cacheSize = DefaultNodesCacheSize; |
| } |
| |
| ~LRUCache() |
| { |
| clear(); |
| } |
| |
| inline void cleanCache() |
| { |
| Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); |
| |
| auto it = cachedItems.rbegin(); |
| while (cachedItemsMap.size() > cacheSize) { |
| // Do not trash elements with children |
| // Workaround QTreeView bugs which caches the children indexes for very long time |
| while (it->second->hasChildren && it != cachedItems.rend()) |
| ++it; |
| |
| if (it == cachedItems.rend()) |
| break; |
| |
| cachedItemsMap.erase(it->first); |
| delete it->second; |
| cachedItems.erase((++it).base()); |
| } |
| Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); |
| } |
| |
| void setCacheSize(size_t rootCacheSize) |
| { |
| cacheSize = rootCacheSize; |
| cleanCache(); |
| cachedItemsMap.reserve(rootCacheSize); |
| } |
| |
| void changeKeys(Key key, Key delta) { |
| std::vector<std::pair<Key, CacheIterator>> changed; |
| auto it = cachedItemsMap.begin(); |
| while (it != cachedItemsMap.end()) { |
| if (it->first >= key) { |
| changed.emplace_back(it->first + delta, it->second); |
| it->second->first += delta; |
| it = cachedItemsMap.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| for (auto pair : changed) |
| cachedItemsMap[pair.first] = pair.second; |
| } |
| |
| void insert(Key key, Value *value) |
| { |
| changeKeys(key, 1); |
| ensure(key, value); |
| Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); |
| } |
| |
| void ensure(Key key, Value *value) |
| { |
| cachedItems.emplace_front(key, value); |
| cachedItemsMap[key] = cachedItems.begin(); |
| cleanCache(); |
| } |
| |
| void remove(Key key) |
| { |
| auto it = cachedItemsMap.find(key); |
| if (it != cachedItemsMap.end()) { |
| delete it->second->second; |
| cachedItems.erase(it->second); |
| cachedItemsMap.erase(it); |
| } |
| changeKeys(key, -1); |
| Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); |
| } |
| |
| Value *get(Key key) |
| { |
| auto it = cachedItemsMap.find(key); |
| if (it == cachedItemsMap.end()) |
| return nullptr; |
| |
| // Move the accessed item to front |
| cachedItems.splice(cachedItems.begin(), cachedItems, it->second); |
| Q_ASSERT(it->second->first == key); |
| return it->second->second; |
| } |
| |
| Key find(Value *val) |
| { |
| for (auto it = cachedItemsMap.begin(); it != cachedItemsMap.end(); ++it) { |
| if (it->second->second == val) |
| return it->first; |
| } |
| Q_ASSERT_X(false, __FUNCTION__, "Value not found"); |
| return Key{}; |
| } |
| |
| bool exists(Value *val) |
| { |
| for (const auto &pair : cachedItems) |
| if (pair.second == val) |
| return true; |
| |
| return false; |
| } |
| |
| bool exists(Key key) |
| { |
| return cachedItemsMap.find(key) != cachedItemsMap.end(); |
| } |
| |
| size_t size() |
| { |
| return cachedItemsMap.size(); |
| } |
| |
| void clear() |
| { |
| for (const auto &pair : cachedItems) |
| delete pair.second; |
| cachedItems.clear(); |
| cachedItemsMap.clear(); |
| } |
| }; |
| |
| class QAbstractItemModelReplicaImplementation; |
| struct CacheData |
| { |
| QAbstractItemModelReplicaImplementation *replicaModel; |
| CacheData *parent; |
| CachedRowEntry cachedRowEntry; |
| |
| bool hasChildren; |
| LRUCache<int, CacheData> children; |
| int columnCount; |
| int rowCount; |
| |
| explicit CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem = nullptr); |
| |
| ~CacheData(); |
| |
| void ensureChildren(int start, int end) |
| { |
| for (int i = start; i <= end; ++i) |
| if (!children.exists(i)) |
| children.ensure(i, new CacheData(replicaModel, this)); |
| } |
| |
| void insertChildren(int start, int end) { |
| Q_ASSERT_X(start >= 0 && start <= end, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2")).arg(start).arg(end))); |
| for (int i = start; i <= end; ++i) { |
| auto cacheData = new CacheData(replicaModel, this); |
| cacheData->columnCount = columnCount; |
| children.insert(i, cacheData); |
| ++rowCount; |
| } |
| if (rowCount) |
| hasChildren = true; |
| } |
| void removeChildren(int start, int end) { |
| Q_ASSERT_X(start >= 0 && start <= end && end < rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2 < %3")).arg(start).arg(end).arg(rowCount))); |
| for (int i = end; i >= start; --i) { |
| children.remove(i); |
| --rowCount; |
| } |
| hasChildren = rowCount; |
| } |
| void clear() { |
| cachedRowEntry.clear(); |
| children.clear(); |
| hasChildren = false; |
| columnCount = 0; |
| rowCount = 0; |
| } |
| }; |
| |
| struct RequestedData |
| { |
| IndexList start; |
| IndexList end; |
| QVector<int> roles; |
| }; |
| |
| struct RequestedHeaderData |
| { |
| int role; |
| int section; |
| Qt::Orientation orientation; |
| }; |
| |
| class SizeWatcher : public QRemoteObjectPendingCallWatcher |
| { |
| Q_OBJECT |
| public: |
| SizeWatcher(IndexList _parentList, const QRemoteObjectPendingReply<QSize> &reply) |
| : QRemoteObjectPendingCallWatcher(reply), |
| parentList(_parentList) {} |
| IndexList parentList; |
| }; |
| |
| class RowWatcher : public QRemoteObjectPendingCallWatcher |
| { |
| Q_OBJECT |
| public: |
| RowWatcher(IndexList _start, IndexList _end, QVector<int> _roles, const QRemoteObjectPendingReply<DataEntries> &reply) |
| : QRemoteObjectPendingCallWatcher(reply), |
| start(_start), |
| end(_end), |
| roles(_roles) {} |
| IndexList start, end; |
| QVector<int> roles; |
| }; |
| |
| class HeaderWatcher : public QRemoteObjectPendingCallWatcher |
| { |
| Q_OBJECT |
| public: |
| HeaderWatcher(QVector<Qt::Orientation> _orientations, QVector<int> _sections, QVector<int> _roles, const QRemoteObjectPendingReply<QVariantList> &reply) |
| : QRemoteObjectPendingCallWatcher(reply), |
| orientations(_orientations), |
| sections(_sections), |
| roles(_roles) {} |
| QVector<Qt::Orientation> orientations; |
| QVector<int> sections, roles; |
| }; |
| |
| class QAbstractItemModelReplicaImplementation : public QRemoteObjectReplica |
| { |
| Q_OBJECT |
| //TODO Use an input name for the model on the Replica side |
| Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "ServerModelAdapter") |
| Q_PROPERTY(QVector<int> availableRoles READ availableRoles NOTIFY availableRolesChanged) |
| Q_PROPERTY(QIntHash roleNames READ roleNames) |
| public: |
| QAbstractItemModelReplicaImplementation(); |
| QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name); |
| ~QAbstractItemModelReplicaImplementation() override; |
| void initialize() override; |
| static void registerMetatypes(); |
| |
| inline const QVector<int> &availableRoles() const |
| { |
| if (m_availableRoles.isEmpty()) |
| m_availableRoles = propAsVariant(0).value<QVector<int> >(); |
| return m_availableRoles; |
| } |
| |
| QHash<int, QByteArray> roleNames() const |
| { |
| QIntHash roles = propAsVariant(1).value<QIntHash>(); |
| return roles; |
| } |
| |
| void setModel(QAbstractItemModelReplica *model); |
| bool clearCache(const IndexList &start, const IndexList &end, const QVector<int> &roles); |
| |
| Q_SIGNALS: |
| void availableRolesChanged(); |
| void dataChanged(IndexList topLeft, IndexList bottomRight, QVector<int> roles); |
| void rowsInserted(IndexList parent, int first, int last); |
| void rowsRemoved(IndexList parent, int first, int last); |
| void rowsMoved(IndexList parent, int start, int end, IndexList destination, int row); |
| void currentChanged(IndexList current, IndexList previous); |
| void modelReset(); |
| void headerDataChanged(Qt::Orientation,int,int); |
| void columnsInserted(IndexList parent, int first, int last); |
| |
| public Q_SLOTS: |
| QRemoteObjectPendingReply<QSize> replicaSizeRequest(IndexList parentList) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSizeRequest(IndexList)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(parentList); |
| return QRemoteObjectPendingReply<QSize>(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); |
| } |
| QRemoteObjectPendingReply<DataEntries> replicaRowRequest(IndexList start, IndexList end, QVector<int> roles) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaRowRequest(IndexList,IndexList,QVector<int>)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(start) << QVariant::fromValue(end) << QVariant::fromValue(roles); |
| return QRemoteObjectPendingReply<DataEntries>(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); |
| } |
| QRemoteObjectPendingReply<QVariantList> replicaHeaderRequest(QVector<Qt::Orientation> orientations, QVector<int> sections, QVector<int> roles) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaHeaderRequest(QVector<Qt::Orientation>,QVector<int>,QVector<int>)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(orientations) << QVariant::fromValue(sections) << QVariant::fromValue(roles); |
| return QRemoteObjectPendingReply<QVariantList>(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); |
| } |
| void replicaSetCurrentIndex(IndexList index, QItemSelectionModel::SelectionFlags command) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetCurrentIndex(IndexList,QItemSelectionModel::SelectionFlags)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(index) << QVariant::fromValue(command); |
| send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); |
| } |
| void replicaSetData(IndexList index, const QVariant &value, int role) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetData(IndexList,QVariant,int)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(index) << QVariant::fromValue(value) << QVariant::fromValue(role); |
| send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); |
| } |
| QRemoteObjectPendingReply<MetaAndDataEntries> replicaCacheRequest(size_t size, QVector<int> roles) |
| { |
| static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaCacheRequest(size_t,QVector<int>)"); |
| QVariantList __repc_args; |
| __repc_args << QVariant::fromValue(size) << QVariant::fromValue(roles); |
| return QRemoteObjectPendingReply<MetaAndDataEntries>(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); |
| } |
| void onHeaderDataChanged(Qt::Orientation orientation, int first, int last); |
| void onDataChanged(const IndexList &start, const IndexList &end, const QVector<int> &roles); |
| void onRowsInserted(const IndexList &parent, int start, int end); |
| void onRowsRemoved(const IndexList &parent, int start, int end); |
| void onColumnsInserted(const IndexList &parent, int start, int end); |
| void onRowsMoved(IndexList srcParent, int srcRow, int count, IndexList destParent, int destRow); |
| void onCurrentChanged(IndexList current, IndexList previous); |
| void onModelReset(); |
| void requestedData(QRemoteObjectPendingCallWatcher *); |
| void requestedHeaderData(QRemoteObjectPendingCallWatcher *); |
| void init(); |
| void fetchPendingData(); |
| void fetchPendingHeaderData(); |
| void handleInitDone(QRemoteObjectPendingCallWatcher *watcher); |
| void handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher); |
| void handleSizeDone(QRemoteObjectPendingCallWatcher *watcher); |
| void onReplicaCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); |
| void fillCache(const IndexValuePair &pair,const QVector<int> &roles); |
| |
| public: |
| QScopedPointer<QItemSelectionModel> m_selectionModel; |
| QVector<CacheEntry> m_headerData[2]; |
| |
| CacheData m_rootItem; |
| inline CacheData* cacheData(const QModelIndex &index) const { |
| if (!index.isValid()) |
| return const_cast<CacheData*>(&m_rootItem); |
| if (index.internalPointer()) { |
| auto parent = static_cast<CacheData*>(index.internalPointer()); |
| if (m_activeParents.find(parent) != m_activeParents.end()) |
| return parent->children.get(index.row()); |
| } |
| return nullptr; |
| } |
| inline CacheData* cacheData(const IndexList &index) const { |
| return cacheData(toQModelIndex(index, q)); |
| } |
| inline CacheData* createCacheData(const IndexList &index) const { |
| auto modelIndex = toQModelIndex(index, q); |
| cacheData(modelIndex.parent())->ensureChildren(modelIndex.row() , modelIndex.row()); |
| return cacheData(modelIndex); |
| } |
| inline CacheEntry* cacheEntry(const QModelIndex &index) const { |
| auto data = cacheData(index); |
| if (!data || index.column() < 0 || index.column() >= data->cachedRowEntry.size()) |
| return nullptr; |
| CacheEntry &entry = data->cachedRowEntry[index.column()]; |
| return &entry; |
| } |
| inline CacheEntry* cacheEntry(const IndexList &index) const { |
| return cacheEntry(toQModelIndex(index, q)); |
| } |
| |
| QRemoteObjectPendingCallWatcher *doModelReset(); |
| void initializeModelConnections(); |
| |
| bool m_initDone = false; |
| QVector<RequestedData> m_requestedData; |
| QVector<RequestedHeaderData> m_requestedHeaderData; |
| QVector<QRemoteObjectPendingCallWatcher*> m_pendingRequests; |
| QAbstractItemModelReplica *q; |
| mutable QVector<int> m_availableRoles; |
| std::unordered_set<CacheData*> m_activeParents; |
| QtRemoteObjects::InitialAction m_initialAction; |
| QVector<int> m_initialFetchRolesHint; |
| }; |
| |
| QT_END_NAMESPACE |
| |
| #endif // QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H |