| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qremoteobjectabstractitemmodelreplica.h" |
| #include "qremoteobjectabstractitemmodelreplica_p.h" |
| |
| #include "qremoteobjectnode.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qrect.h> |
| #include <QtCore/qpoint.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| inline QDebug operator<<(QDebug stream, const RequestedData &data) |
| { |
| return stream.nospace() << "RequestedData[start=" << data.start << ", end=" << data.end << ", roles=" << data.roles << "]"; |
| } |
| |
| CacheData::CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem) |
| : replicaModel(model) |
| , parent(parentItem) |
| , hasChildren(false) |
| , columnCount(0) |
| , rowCount(0) |
| { |
| if (parent) |
| replicaModel->m_activeParents.insert(parent); |
| } |
| |
| CacheData::~CacheData() { |
| if (parent && !replicaModel->m_activeParents.empty()) |
| replicaModel->m_activeParents.erase(this); |
| } |
| |
| QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation() |
| : QRemoteObjectReplica() |
| , m_selectionModel(0) |
| , m_rootItem(this) |
| { |
| QAbstractItemModelReplicaImplementation::registerMetatypes(); |
| initializeModelConnections(); |
| connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{ |
| m_availableRoles.clear(); |
| }); |
| } |
| |
| QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name) |
| : QRemoteObjectReplica(ConstructWithNode) |
| , m_selectionModel(0) |
| , m_rootItem(this) |
| { |
| QAbstractItemModelReplicaImplementation::registerMetatypes(); |
| initializeModelConnections(); |
| initializeNode(node, name); |
| connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{ |
| m_availableRoles.clear(); |
| }); |
| } |
| |
| QAbstractItemModelReplicaImplementation::~QAbstractItemModelReplicaImplementation() |
| { |
| m_rootItem.clear(); |
| qDeleteAll(m_pendingRequests); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::initialize() |
| { |
| QVariantList properties; |
| properties << QVariant::fromValue(QVector<int>()); |
| properties << QVariant::fromValue(QIntHash()); |
| setProperties(properties); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::registerMetatypes() |
| { |
| static bool alreadyRegistered = false; |
| if (alreadyRegistered) |
| return; |
| |
| alreadyRegistered = true; |
| qRegisterMetaType<QAbstractItemModel*>(); |
| qRegisterMetaType<Qt::Orientation>(); |
| qRegisterMetaType<QVector<Qt::Orientation> >(); |
| qRegisterMetaTypeStreamOperators<ModelIndex>(); |
| qRegisterMetaTypeStreamOperators<IndexList>(); |
| qRegisterMetaTypeStreamOperators<DataEntries>(); |
| qRegisterMetaTypeStreamOperators<MetaAndDataEntries>(); |
| qRegisterMetaTypeStreamOperators<Qt::Orientation>(); |
| qRegisterMetaTypeStreamOperators<QVector<Qt::Orientation> >(); |
| qRegisterMetaTypeStreamOperators<QItemSelectionModel::SelectionFlags>(); |
| qRegisterMetaType<QItemSelectionModel::SelectionFlags>(); |
| qRegisterMetaType<QSize>(); |
| qRegisterMetaType<QIntHash>(); |
| qRegisterMetaTypeStreamOperators<QIntHash>(); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::initializeModelConnections() |
| { |
| connect(this, &QAbstractItemModelReplicaImplementation::dataChanged, this, &QAbstractItemModelReplicaImplementation::onDataChanged); |
| connect(this, &QAbstractItemModelReplicaImplementation::rowsInserted, this, &QAbstractItemModelReplicaImplementation::onRowsInserted); |
| connect(this, &QAbstractItemModelReplicaImplementation::columnsInserted, this, &QAbstractItemModelReplicaImplementation::onColumnsInserted); |
| connect(this, &QAbstractItemModelReplicaImplementation::rowsRemoved, this, &QAbstractItemModelReplicaImplementation::onRowsRemoved); |
| connect(this, &QAbstractItemModelReplicaImplementation::rowsMoved, this, &QAbstractItemModelReplicaImplementation::onRowsMoved); |
| connect(this, &QAbstractItemModelReplicaImplementation::currentChanged, this, &QAbstractItemModelReplicaImplementation::onCurrentChanged); |
| connect(this, &QAbstractItemModelReplicaImplementation::modelReset, this, &QAbstractItemModelReplicaImplementation::onModelReset); |
| connect(this, &QAbstractItemModelReplicaImplementation::headerDataChanged, this, &QAbstractItemModelReplicaImplementation::onHeaderDataChanged); |
| } |
| |
| inline void removeIndexFromRow(const QModelIndex &index, const QVector<int> &roles, CachedRowEntry *entry) |
| { |
| CachedRowEntry &entryRef = *entry; |
| if (index.column() < entryRef.size()) { |
| CacheEntry &entry = entryRef[index.column()]; |
| if (roles.isEmpty()) { |
| entry.data.clear(); |
| } else { |
| for (int role : roles) |
| entry.data.remove(role); |
| } |
| } |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
| { |
| Q_UNUSED(previous) |
| IndexList currentIndex = toModelIndexList(current, q); |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentIndex; |
| replicaSetCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::setModel(QAbstractItemModelReplica *model) |
| { |
| q = model; |
| setParent(model); |
| m_selectionModel.reset(new QItemSelectionModel(model)); |
| connect(m_selectionModel.data(), &QItemSelectionModel::currentChanged, this, &QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged); |
| } |
| |
| bool QAbstractItemModelReplicaImplementation::clearCache(const IndexList &start, const IndexList &end, const QVector<int> &roles = QVector<int>()) |
| { |
| Q_ASSERT(start.size() == end.size()); |
| |
| bool ok = true; |
| const QModelIndex startIndex = toQModelIndex(start, q, &ok); |
| if (!ok) |
| return false; |
| const QModelIndex endIndex = toQModelIndex(end, q, &ok); |
| if (!ok) |
| return false; |
| Q_ASSERT(startIndex.isValid()); |
| Q_ASSERT(endIndex.isValid()); |
| Q_ASSERT(startIndex.parent() == endIndex.parent()); |
| Q_UNUSED(endIndex); |
| QModelIndex parentIndex = startIndex.parent(); |
| auto parentItem = cacheData(parentIndex); |
| |
| const int startRow = start.last().row; |
| const int lastRow = end.last().row; |
| const int startColumn = start.last().column; |
| const int lastColumn = end.last().column; |
| for (int row = startRow; row <= lastRow; ++row) { |
| Q_ASSERT_X(row >= 0 && row < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(row).arg(parentItem->rowCount))); |
| auto item = parentItem->children.get(row); |
| if (item) { |
| CachedRowEntry *entry = &(item->cachedRowEntry); |
| for (int column = startColumn; column <= lastColumn; ++column) |
| removeIndexFromRow(q->index(row, column, parentIndex), roles, entry); |
| } |
| } |
| return true; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onDataChanged(const IndexList &start, const IndexList &end, const QVector<int> &roles) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "roles=" << roles; |
| |
| // we need to clear the cache to make sure the new remote data is fetched if the new data call is happening |
| if (clearCache(start, end, roles)) { |
| bool ok = true; |
| const QModelIndex startIndex = toQModelIndex(start, q, &ok); |
| if (!ok) |
| return; |
| const QModelIndex endIndex = toQModelIndex(end, q, &ok); |
| if (!ok) |
| return; |
| Q_ASSERT(startIndex.parent() == endIndex.parent()); |
| auto parentItem = cacheData(startIndex.parent()); |
| int startRow = start.last().row; |
| int endRow = end.last().row; |
| bool dataChanged = false; |
| while (startRow <= endRow) { |
| for (;startRow <= endRow; startRow++) { |
| if (parentItem->children.exists(startRow)) |
| break; |
| } |
| |
| if (startRow > endRow) |
| break; |
| |
| RequestedData data; |
| data.roles = roles; |
| data.start = start; |
| data.start.last().row = startRow; |
| |
| while (startRow <= endRow && parentItem->children.exists(startRow)) |
| ++startRow; |
| |
| data.end = end; |
| data.end.last().row = startRow -1; |
| |
| m_requestedData.append(data); |
| dataChanged = true; |
| } |
| |
| if (dataChanged) |
| QMetaObject::invokeMethod(this, "fetchPendingData", Qt::QueuedConnection); |
| } |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onRowsInserted(const IndexList &parent, int start, int end) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; |
| |
| bool treeFullyLazyLoaded = true; |
| const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded, true); |
| if (!treeFullyLazyLoaded) |
| return; |
| |
| auto parentItem = cacheData(parentIndex); |
| q->beginInsertRows(parentIndex, start, end); |
| parentItem->insertChildren(start, end); |
| q->endInsertRows(); |
| if (!parentItem->hasChildren && parentItem->columnCount > 0) { |
| parentItem->hasChildren = true; |
| emit q->dataChanged(parentIndex, parentIndex); |
| } |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onColumnsInserted(const IndexList &parent, int start, int end) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; |
| |
| bool treeFullyLazyLoaded = true; |
| const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded); |
| if (!treeFullyLazyLoaded) |
| return; |
| |
| //Since we need to support QAIM and models that don't emit columnCountChanged |
| //check if we have a constant columnCount everywhere if thats the case don't insert |
| //more columns |
| auto parentItem = cacheData(parentIndex); |
| auto parentOfParent = parentItem->parent; |
| if (parentOfParent && parentItem != &m_rootItem) |
| if (parentOfParent->columnCount == parentItem->columnCount) |
| return; |
| q->beginInsertColumns(parentIndex, start, end); |
| parentItem->columnCount += end - start + 1; |
| q->endInsertColumns(); |
| if (!parentItem->hasChildren && parentItem->children.size() > 0) { |
| parentItem->hasChildren = true; |
| emit q->dataChanged(parentIndex, parentIndex); |
| } |
| |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onRowsRemoved(const IndexList &parent, int start, int end) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; |
| |
| bool treeFullyLazyLoaded = true; |
| const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded); |
| if (!treeFullyLazyLoaded) |
| return; |
| |
| auto parentItem = cacheData(parentIndex); |
| q->beginRemoveRows(parentIndex, start, end); |
| if (parentItem) |
| parentItem->removeChildren(start, end); |
| q->endRemoveRows(); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onRowsMoved(IndexList srcParent, int srcRow, int count, IndexList destParent, int destRow) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; |
| |
| const QModelIndex sourceParent = toQModelIndex(srcParent, q); |
| const QModelIndex destinationParent = toQModelIndex(destParent, q); |
| Q_ASSERT(!sourceParent.isValid()); |
| Q_ASSERT(!destinationParent.isValid()); |
| q->beginMoveRows(sourceParent, srcRow, count, destinationParent, destRow); |
| //TODO misses parents... |
| IndexList start, end; |
| start << ModelIndex(srcRow, 0); |
| end << ModelIndex(srcRow + count, q->columnCount(sourceParent)-1); |
| clearCache(start, end); |
| IndexList start2, end2; |
| start2 << ModelIndex(destRow, 0); |
| end2 << ModelIndex(destRow + count, q->columnCount(destinationParent)-1); |
| clearCache(start2, end2); |
| q->endMoveRows(); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onCurrentChanged(IndexList current, IndexList previous) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << current << "previous=" << previous; |
| Q_UNUSED(previous); |
| Q_ASSERT(m_selectionModel); |
| bool ok; |
| // If we have several tree models sharing a selection model, we |
| // can't guarantee that all Replicas have the selected cell |
| // available. |
| const QModelIndex currentIndex = toQModelIndex(current, q, &ok); |
| // Ignore selection if we can't find the desired cell. |
| if (ok) |
| m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::handleInitDone(QRemoteObjectPendingCallWatcher *watcher) |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; |
| |
| handleModelResetDone(watcher); |
| m_initDone = true; |
| emit q->initialized(); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher) |
| { |
| QSize size; |
| if (m_initialAction == QtRemoteObjects::FetchRootSize) |
| size = watcher->returnValue().toSize(); |
| else { |
| Q_ASSERT(watcher->returnValue().canConvert<MetaAndDataEntries>()); |
| size = watcher->returnValue().value<MetaAndDataEntries>().size; |
| } |
| |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "size=" << size; |
| |
| q->beginResetModel(); |
| m_rootItem.clear(); |
| if (size.height() > 0) { |
| m_rootItem.rowCount = size.height(); |
| m_rootItem.hasChildren = true; |
| } |
| |
| m_rootItem.columnCount = size.width(); |
| m_headerData[0].resize(size.width()); |
| m_headerData[1].resize(size.height()); |
| if (m_initialAction == QtRemoteObjects::PrefetchData) { |
| auto entries = watcher->returnValue().value<MetaAndDataEntries>(); |
| for (int i = 0; i < entries.data.size(); ++i) |
| fillCache(entries.data[i], entries.roles); |
| } |
| q->endResetModel(); |
| m_pendingRequests.removeAll(watcher); |
| delete watcher; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::handleSizeDone(QRemoteObjectPendingCallWatcher *watcher) |
| { |
| SizeWatcher *sizeWatcher = static_cast<SizeWatcher*>(watcher); |
| const QSize size = sizeWatcher->returnValue().toSize(); |
| auto parentItem = cacheData(sizeWatcher->parentList); |
| const QModelIndex parent = toQModelIndex(sizeWatcher->parentList, q); |
| |
| if (size.width() != parentItem->columnCount) { |
| const int columnCount = std::max(0, parentItem->columnCount); |
| Q_ASSERT_X(size.width() >= parentItem->columnCount, __FUNCTION__, "The column count should only shrink in columnsRemoved!!"); |
| parentItem->columnCount = size.width(); |
| if (size.width() > columnCount) { |
| Q_ASSERT(size.width() > 0); |
| q->beginInsertColumns(parent, columnCount, size.width() - 1); |
| q->endInsertColumns(); |
| } else { |
| Q_ASSERT_X(size.width() == columnCount, __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(size.width()).arg(columnCount))); |
| } |
| } |
| |
| Q_ASSERT_X(size.height() >= parentItem->rowCount, __FUNCTION__, "The new size and the current size should match!!"); |
| if (!parentItem->rowCount) { |
| if (size.height() > 0) { |
| q->beginInsertRows(parent, 0, size.height() - 1); |
| parentItem->rowCount = size.height(); |
| q->endInsertRows(); |
| } |
| } else { |
| Q_ASSERT_X(parentItem->rowCount == size.height(), __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(parentItem->rowCount).arg(size.height()))); |
| } |
| m_pendingRequests.removeAll(watcher); |
| delete watcher; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::init() |
| { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << this->node()->objectName(); |
| QRemoteObjectPendingCallWatcher *watcher = doModelReset(); |
| connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleInitDone); |
| } |
| |
| QRemoteObjectPendingCallWatcher* QAbstractItemModelReplicaImplementation::doModelReset() |
| { |
| qDeleteAll(m_pendingRequests); |
| m_pendingRequests.clear(); |
| IndexList parentList; |
| QRemoteObjectPendingCallWatcher *watcher; |
| if (m_initialAction == QtRemoteObjects::FetchRootSize) { |
| auto call = replicaSizeRequest(parentList); |
| watcher = new SizeWatcher(parentList, call); |
| } else { |
| auto call = replicaCacheRequest(m_rootItem.children.cacheSize, m_initialFetchRolesHint); |
| watcher = new QRemoteObjectPendingCallWatcher(call); |
| } |
| m_pendingRequests.push_back(watcher); |
| return watcher; |
| } |
| |
| inline void fillCacheEntry(CacheEntry *entry, const IndexValuePair &pair, const QVector<int> &roles) |
| { |
| Q_ASSERT(entry); |
| |
| const QVariantList &data = pair.data; |
| Q_ASSERT(roles.size() == data.size()); |
| |
| entry->flags = pair.flags; |
| |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "data.size=" << data.size(); |
| for (int i = 0; i < data.size(); ++i) { |
| const int role = roles[i]; |
| const QVariant dataVal = data[i]; |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "role=" << role << "data=" << dataVal; |
| entry->data[role] = dataVal; |
| } |
| } |
| |
| inline void fillRow(CacheData *item, const IndexValuePair &pair, const QAbstractItemModel *model, const QVector<int> &roles) |
| { |
| CachedRowEntry &rowRef = item->cachedRowEntry; |
| const QModelIndex index = toQModelIndex(pair.index, model); |
| Q_ASSERT(index.isValid()); |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "row=" << index.row() << "column=" << index.column(); |
| if (index.column() == 0) |
| item->hasChildren = pair.hasChildren; |
| bool existed = false; |
| for (int i = 0; i < rowRef.size(); ++i) { |
| if (i == index.column()) { |
| fillCacheEntry(&rowRef[i], pair, roles); |
| existed = true; |
| } |
| } |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "existed=" << existed; |
| if (!existed) { |
| CacheEntry entries; |
| fillCacheEntry(&entries, pair, roles); |
| rowRef.append(entries); |
| } |
| } |
| |
| int collectEntriesForRow(DataEntries* filteredEntries, int row, const DataEntries &entries, int startIndex) |
| { |
| Q_ASSERT(filteredEntries); |
| const int size = entries.data.size(); |
| for (int i = startIndex; i < size; ++i) |
| { |
| const IndexValuePair &pair = entries.data[i]; |
| if (pair.index.last().row == row) |
| filteredEntries->data << pair; |
| else |
| return i; |
| } |
| return size; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::fillCache(const IndexValuePair &pair, const QVector<int> &roles) |
| { |
| if (auto item = createCacheData(pair.index)) { |
| fillRow(item, pair, q, roles); |
| item->rowCount = pair.size.height(); |
| item->columnCount = pair.size.width(); |
| } |
| for (const auto &it : pair.children) |
| fillCache(it, roles); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::requestedData(QRemoteObjectPendingCallWatcher *qobject) |
| { |
| RowWatcher *watcher = static_cast<RowWatcher *>(qobject); |
| Q_ASSERT(watcher); |
| Q_ASSERT(watcher->start.size() == watcher->end.size()); |
| |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << watcher->start << "end=" << watcher->end; |
| |
| IndexList parentList = watcher->start; |
| Q_ASSERT(!parentList.isEmpty()); |
| parentList.pop_back(); |
| auto parentItem = cacheData(parentList); |
| DataEntries entries = watcher->returnValue().value<DataEntries>(); |
| |
| const int rowCount = parentItem->rowCount; |
| const int columnCount = parentItem->columnCount; |
| |
| if (rowCount < 1 || columnCount < 1) |
| return; |
| |
| const int startRow = std::min(watcher->start.last().row, rowCount - 1); |
| const int endRow = std::min(watcher->end.last().row, rowCount - 1); |
| const int startColumn = std::min(watcher->start.last().column, columnCount - 1); |
| const int endColumn = std::min(watcher->end.last().column, columnCount - 1); |
| Q_ASSERT_X(startRow >= 0 && startRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(startRow).arg(parentItem->rowCount))); |
| Q_ASSERT_X(endRow >= 0 && endRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endRow).arg(parentItem->rowCount))); |
| |
| for (int i = 0; i < entries.data.size(); ++i) { |
| IndexValuePair pair = entries.data[i]; |
| if (auto item = createCacheData(pair.index)) |
| fillRow(item, pair, q, watcher->roles); |
| } |
| |
| const QModelIndex parentIndex = toQModelIndex(parentList, q); |
| const QModelIndex startIndex = q->index(startRow, startColumn, parentIndex); |
| const QModelIndex endIndex = q->index(endRow, endColumn, parentIndex); |
| Q_ASSERT(startIndex.isValid()); |
| Q_ASSERT(endIndex.isValid()); |
| emit q->dataChanged(startIndex, endIndex, watcher->roles); |
| m_pendingRequests.removeAll(watcher); |
| delete watcher; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::fetchPendingData() |
| { |
| if (m_requestedData.isEmpty()) |
| return; |
| |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "m_requestedData.size=" << m_requestedData.size(); |
| |
| std::vector<RequestedData> finalRequests; |
| RequestedData curData; |
| for (const RequestedData &data : qExchange(m_requestedData, {})) { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "REQUESTED start=" << data.start << "end=" << data.end << "roles=" << data.roles; |
| |
| Q_ASSERT(!data.start.isEmpty()); |
| Q_ASSERT(!data.end.isEmpty()); |
| Q_ASSERT(data.start.size() == data.end.size()); |
| if (curData.start.isEmpty() || curData.start.last().row == -1 || curData.start.last().column == -1) |
| curData = data; |
| if (curData.start.size() != data.start.size()) { |
| finalRequests.push_back(curData); |
| curData = data; |
| } else { |
| if (data.start.size() > 1) { |
| for (int i = 0; i < data.start.size() - 1; ++i) { |
| if (curData.start[i].row != data.start[i].row || |
| curData.start[i].column != data.start[i].column) { |
| finalRequests.push_back(curData); |
| curData = data; |
| } |
| } |
| } |
| |
| const ModelIndex curIndStart = curData.start.last(); |
| const ModelIndex curIndEnd = curData.end.last(); |
| const ModelIndex dataIndStart = data.start.last(); |
| const ModelIndex dataIndEnd = data.end.last(); |
| const ModelIndex resStart(std::min(curIndStart.row, dataIndStart.row), std::min(curIndStart.column, dataIndStart.column)); |
| const ModelIndex resEnd(std::max(curIndEnd.row, dataIndEnd.row), std::max(curIndEnd.column, dataIndEnd.column)); |
| QVector<int> roles = curData.roles; |
| if (!curData.roles.isEmpty()) { |
| for (int role : data.roles) { |
| if (!curData.roles.contains(role)) |
| roles.append(role); |
| } |
| } |
| QRect firstRect( QPoint(curIndStart.row, curIndStart.column), QPoint(curIndEnd.row, curIndEnd.column)); |
| QRect secondRect( QPoint(dataIndStart.row, dataIndStart.column), QPoint(dataIndEnd.row, dataIndEnd.column)); |
| |
| const bool borders = (qAbs(curIndStart.row - dataIndStart.row) == 1) || |
| (qAbs(curIndStart.column - dataIndStart.column) == 1) || |
| (qAbs(curIndEnd.row - dataIndEnd.row) == 1) || |
| (qAbs(curIndEnd.column - dataIndEnd.column) == 1); |
| |
| if ((resEnd.row - resStart.row < 100) && (firstRect.intersects(secondRect) || borders)) { |
| IndexList start = curData.start; |
| start.pop_back(); |
| start.push_back(resStart); |
| IndexList end = curData.end; |
| end.pop_back(); |
| end.push_back(resEnd); |
| curData.start = start; |
| curData.end = end; |
| curData.roles = roles; |
| Q_ASSERT(!start.isEmpty()); |
| Q_ASSERT(!end.isEmpty()); |
| } else { |
| finalRequests.push_back(curData); |
| curData = data; |
| } |
| } |
| } |
| finalRequests.push_back(curData); |
| //qCDebug(QT_REMOTEOBJECT_MODELS) << "Final requests" << finalRequests; |
| int rows = 0; |
| // There is no point to eat more than can chew |
| for (auto it = finalRequests.rbegin(); it != finalRequests.rend() && size_t(rows) < m_rootItem.children.cacheSize; ++it) { |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "FINAL start=" << it->start << "end=" << it->end << "roles=" << it->roles; |
| |
| QRemoteObjectPendingReply<DataEntries> reply = replicaRowRequest(it->start, it->end, it->roles); |
| RowWatcher *watcher = new RowWatcher(it->start, it->end, it->roles, reply); |
| rows += 1 + it->end.first().row - it->start.first().row; |
| m_pendingRequests.push_back(watcher); |
| connect(watcher, &RowWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedData); |
| } |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onModelReset() |
| { |
| if (!m_initDone) |
| return; |
| |
| qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; |
| QRemoteObjectPendingCallWatcher *watcher = doModelReset(); |
| connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleModelResetDone); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) |
| { |
| // TODO clean cache |
| const int index = orientation == Qt::Horizontal ? 0 : 1; |
| QVector<CacheEntry> &entries = m_headerData[index]; |
| for (int i = first; i < last; ++i ) |
| entries[i].data.clear(); |
| emit q->headerDataChanged(orientation, first, last); |
| } |
| |
| void QAbstractItemModelReplicaImplementation::fetchPendingHeaderData() |
| { |
| QVector<int> roles; |
| QVector<int> sections; |
| QVector<Qt::Orientation> orientations; |
| for (const RequestedHeaderData &data : qAsConst(m_requestedHeaderData)) { |
| roles.push_back(data.role); |
| sections.push_back(data.section); |
| orientations.push_back(data.orientation); |
| } |
| QRemoteObjectPendingReply<QVariantList> reply = replicaHeaderRequest(orientations, sections, roles); |
| HeaderWatcher *watcher = new HeaderWatcher(orientations, sections, roles, reply); |
| connect(watcher, &HeaderWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedHeaderData); |
| m_requestedHeaderData.clear(); |
| m_pendingRequests.push_back(watcher); |
| } |
| |
| static inline QVector<QPair<int, int> > listRanges(const QVector<int> &list) |
| { |
| QVector<QPair<int, int> > result; |
| if (!list.isEmpty()) { |
| QPair<int, int> currentElem = qMakePair(list.first(), list.first()); |
| QVector<int>::const_iterator end = list.end(); |
| for (QVector<int>::const_iterator it = list.constBegin() + 1; it != end; ++it) { |
| if (currentElem.first == *it +1) |
| currentElem.first = *it; |
| else if ( currentElem.second == *it -1) |
| currentElem.second = *it; |
| else if (currentElem.first <= *it && currentElem.second >= *it) |
| continue; |
| else { |
| result.push_back(currentElem); |
| currentElem.first = *it; |
| currentElem.second = *it; |
| } |
| } |
| result.push_back(currentElem); |
| } |
| return result; |
| } |
| |
| void QAbstractItemModelReplicaImplementation::requestedHeaderData(QRemoteObjectPendingCallWatcher *qobject) |
| { |
| HeaderWatcher *watcher = static_cast<HeaderWatcher *>(qobject); |
| Q_ASSERT(watcher); |
| |
| QVariantList data = watcher->returnValue().value<QVariantList>(); |
| Q_ASSERT(watcher->orientations.size() == data.size()); |
| Q_ASSERT(watcher->sections.size() == data.size()); |
| Q_ASSERT(watcher->roles.size() == data.size()); |
| QVector<int> horizontalSections; |
| QVector<int> verticalSections; |
| |
| for (int i = 0; i < data.size(); ++i) { |
| if (watcher->orientations[i] == Qt::Horizontal) |
| horizontalSections.append(watcher->sections[i]); |
| else |
| verticalSections.append(watcher->sections[i]); |
| const int index = watcher->orientations[i] == Qt::Horizontal ? 0 : 1; |
| const int role = watcher->roles[i]; |
| QHash<int, QVariant> &dat = m_headerData[index][watcher->sections[i]].data; |
| dat[role] = data[i]; |
| } |
| QVector<QPair<int, int> > horRanges = listRanges(horizontalSections); |
| QVector<QPair<int, int> > verRanges = listRanges(verticalSections); |
| |
| for (int i = 0; i < horRanges.size(); ++i) |
| emit q->headerDataChanged(Qt::Horizontal, horRanges[i].first, horRanges[i].second); |
| for (int i = 0; i < verRanges.size(); ++i) |
| emit q->headerDataChanged(Qt::Vertical, verRanges[i].first, verRanges[i].second); |
| m_pendingRequests.removeAll(watcher); |
| delete watcher; |
| } |
| |
| QAbstractItemModelReplica::QAbstractItemModelReplica(QAbstractItemModelReplicaImplementation *rep, QtRemoteObjects::InitialAction action, const QVector<int> &rolesHint) |
| : QAbstractItemModel() |
| , d(rep) |
| { |
| d->m_initialAction = action; |
| d->m_initialFetchRolesHint = rolesHint; |
| |
| rep->setModel(this); |
| connect(rep, &QAbstractItemModelReplicaImplementation::initialized, d.data(), &QAbstractItemModelReplicaImplementation::init); |
| } |
| |
| QAbstractItemModelReplica::~QAbstractItemModelReplica() |
| { |
| } |
| |
| static QVariant findData(const CachedRowEntry &row, const QModelIndex &index, int role, bool *cached = 0) |
| { |
| if (index.column() < row.size()) { |
| const CacheEntry &entry = row[index.column()]; |
| QHash<int, QVariant>::ConstIterator it = entry.data.constFind(role); |
| if (it != entry.data.constEnd()) { |
| if (cached) |
| *cached = true; |
| return it.value(); |
| } |
| } |
| if (cached) |
| *cached = false; |
| return QVariant(); |
| } |
| |
| QItemSelectionModel* QAbstractItemModelReplica::selectionModel() const |
| { |
| return d->m_selectionModel.data(); |
| } |
| |
| bool QAbstractItemModelReplica::setData(const QModelIndex &index, const QVariant &value, int role) |
| { |
| if (role == Qt::UserRole - 1) { |
| auto parent = d->cacheData(index); |
| if (!parent) |
| return false; |
| bool ok = true; |
| auto row = value.toInt(&ok); |
| if (ok) |
| parent->ensureChildren(row, row); |
| return ok; |
| } |
| if (!index.isValid()) |
| return false; |
| if (index.row() < 0 || index.row() >= rowCount(index.parent())) |
| return false; |
| if (index.column() < 0 || index.column() >= columnCount(index.parent())) |
| return false; |
| |
| const QVector<int > &availRoles = availableRoles(); |
| const QVector<int>::const_iterator res = std::find(availRoles.begin(), availRoles.end(), role); |
| if (res == availRoles.end()) { |
| qCWarning(QT_REMOTEOBJECT_MODELS) << "Tried to setData for index" << index << "on a not supported role" << role; |
| return false; |
| } |
| // sendInvocationRequest to change server side data; |
| d->replicaSetData(toModelIndexList(index, this), value, role); |
| return true; |
| } |
| |
| QVariant QAbstractItemModelReplica::data(const QModelIndex & index, int role) const |
| { |
| |
| if (!d->isInitialized()) { |
| qCDebug(QT_REMOTEOBJECT_MODELS)<<"Data not initialized yet"; |
| return QVariant(); |
| } |
| |
| if (!index.isValid()) |
| return QVariant(); |
| |
| if (!availableRoles().contains(role)) |
| return QVariant(); |
| |
| auto item = d->cacheData(index); |
| if (item) { |
| bool cached = false; |
| QVariant result = findData(item->cachedRowEntry, index, role, &cached); |
| if (cached) |
| return result; |
| } |
| |
| auto parentItem = d->cacheData(index.parent()); |
| Q_ASSERT(parentItem); |
| Q_ASSERT(index.row() < parentItem->rowCount); |
| const int row = index.row(); |
| IndexList parentList = toModelIndexList(index.parent(), this); |
| IndexList start = IndexList() << parentList << ModelIndex(row, 0); |
| IndexList end = IndexList() << parentList << ModelIndex(row, std::max(0, parentItem->columnCount - 1)); |
| Q_ASSERT(toQModelIndex(start, this).isValid()); |
| |
| RequestedData data; |
| QVector<int> roles; |
| roles << role; |
| data.start = start; |
| data.end = end; |
| data.roles = roles; |
| d->m_requestedData.push_back(data); |
| qCDebug(QT_REMOTEOBJECT_MODELS) << "FETCH PENDING DATA" << start << end << roles; |
| QMetaObject::invokeMethod(d.data(), "fetchPendingData", Qt::QueuedConnection); |
| return QVariant{}; |
| } |
| QModelIndex QAbstractItemModelReplica::parent(const QModelIndex &index) const |
| { |
| if (!index.isValid() || !index.internalPointer()) |
| return QModelIndex(); |
| auto parent = static_cast<CacheData*>(index.internalPointer()); |
| Q_ASSERT(parent); |
| if (parent == &d->m_rootItem) |
| return QModelIndex(); |
| if (d->m_activeParents.find(parent) == d->m_activeParents.end() || d->m_activeParents.find(parent->parent) == d->m_activeParents.end()) |
| return QModelIndex(); |
| int row = parent->parent->children.find(parent); |
| Q_ASSERT(row >= 0); |
| return createIndex(row, 0, parent->parent); |
| } |
| QModelIndex QAbstractItemModelReplica::index(int row, int column, const QModelIndex &parent) const |
| { |
| auto parentItem = d->cacheData(parent); |
| if (!parentItem) |
| return QModelIndex(); |
| |
| Q_ASSERT_X((parent.isValid() && parentItem && parentItem != &d->m_rootItem) || (!parent.isValid() && parentItem == &d->m_rootItem), __FUNCTION__, qPrintable(QString(QLatin1String("isValid=%1 equals=%2")).arg(parent.isValid()).arg(parentItem == &d->m_rootItem))); |
| |
| // hmpf, following works around a Q_ASSERT-bug in QAbstractItemView::setModel which does just call |
| // d->model->index(0,0) without checking the range before-hand what triggers our assert in case the |
| // model is empty when view::setModel is called :-/ So, work around with the following; |
| if (row < 0 || row >= parentItem->rowCount) |
| return QModelIndex(); |
| |
| if (column < 0 || column >= parentItem->columnCount) |
| return QModelIndex(); |
| |
| if (parentItem != &d->m_rootItem) |
| parentItem->ensureChildren(row, row); |
| return createIndex(row, column, reinterpret_cast<void*>(parentItem)); |
| } |
| bool QAbstractItemModelReplica::hasChildren(const QModelIndex &parent) const |
| { |
| auto parentItem = d->cacheData(parent); |
| if (parent.isValid() && parent.column() != 0) |
| return false; |
| else |
| return parentItem ? parentItem->hasChildren : false; |
| } |
| int QAbstractItemModelReplica::rowCount(const QModelIndex &parent) const |
| { |
| auto parentItem = d->cacheData(parent); |
| const bool canHaveChildren = parentItem && parentItem->hasChildren && !parentItem->rowCount && parent.column() == 0; |
| if (canHaveChildren) { |
| IndexList parentList = toModelIndexList(parent, this); |
| QRemoteObjectPendingReply<QSize> reply = d->replicaSizeRequest(parentList); |
| SizeWatcher *watcher = new SizeWatcher(parentList, reply); |
| connect(watcher, &SizeWatcher::finished, d.data(), &QAbstractItemModelReplicaImplementation::handleSizeDone); |
| } else if (parent.column() > 0) { |
| return 0; |
| } |
| |
| return parentItem ? parentItem->rowCount : 0; |
| } |
| int QAbstractItemModelReplica::columnCount(const QModelIndex &parent) const |
| { |
| if (parent.isValid() && parent.column() > 0) |
| return 0; |
| auto parentItem = d->cacheData(parent); |
| if (!parentItem) |
| return 0; |
| while (parentItem->columnCount < 0 && parentItem->parent) |
| parentItem = parentItem->parent; |
| return std::max(0, parentItem->columnCount); |
| } |
| |
| QVariant QAbstractItemModelReplica::headerData(int section, Qt::Orientation orientation, int role) const |
| { |
| const int index = orientation == Qt::Horizontal ? 0 : 1; |
| const QVector<CacheEntry> elem = d->m_headerData[index]; |
| if (section >= elem.size()) |
| return QVariant(); |
| |
| const QHash<int, QVariant> &dat = elem.at(section).data; |
| QHash<int, QVariant>::ConstIterator it = dat.constFind(role); |
| if (it != dat.constEnd()) |
| return it.value(); |
| |
| RequestedHeaderData data; |
| data.role = role; |
| data.section = section; |
| data.orientation = orientation; |
| d->m_requestedHeaderData.push_back(data); |
| QMetaObject::invokeMethod(d.data(), "fetchPendingHeaderData", Qt::QueuedConnection); |
| return QVariant(); |
| } |
| |
| Qt::ItemFlags QAbstractItemModelReplica::flags(const QModelIndex &index) const |
| { |
| CacheEntry *entry = d->cacheEntry(index); |
| return entry ? entry->flags : Qt::NoItemFlags; |
| } |
| |
| bool QAbstractItemModelReplica::isInitialized() const |
| { |
| return d->isInitialized(); |
| } |
| |
| bool QAbstractItemModelReplica::hasData(const QModelIndex &index, int role) const |
| { |
| if (!d->isInitialized() || !index.isValid()) |
| return false; |
| auto item = d->cacheData(index); |
| if (!item) |
| return false; |
| bool cached = false; |
| const CachedRowEntry &entry = item->cachedRowEntry; |
| QVariant result = findData(entry, index, role, &cached); |
| Q_UNUSED(result); |
| return cached; |
| } |
| |
| size_t QAbstractItemModelReplica::rootCacheSize() const |
| { |
| return d->m_rootItem.children.cacheSize; |
| } |
| |
| void QAbstractItemModelReplica::setRootCacheSize(size_t rootCacheSize) |
| { |
| d->m_rootItem.children.setCacheSize(rootCacheSize); |
| } |
| |
| QVector<int> QAbstractItemModelReplica::availableRoles() const |
| { |
| return d->availableRoles(); |
| } |
| |
| QHash<int, QByteArray> QAbstractItemModelReplica::roleNames() const |
| { |
| return d->roleNames(); |
| } |
| |
| QT_END_NAMESPACE |