summaryrefslogtreecommitdiffstats
path: root/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp
diff options
context:
space:
mode:
authorKevin Funk <kevin.funk.ford@kdab.com>2016-03-16 14:31:14 +0100
committerKevin Funk <kevin.funk@kdab.com>2016-03-21 10:57:58 +0000
commit1d687cebecacc9091e488ec8f8359242a7ce6725 (patch)
tree72eb1f1728820417765570fb9ef09781e25c8643 /src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp
parent0e294632142a5f34036cdbe60c5daae2f9f72e67 (diff)
Rename QAbstractItemFoo -> QAbstractItemModelFoo
Source-incompatible change, but worth it, IMO. Change-Id: I2e1c716dafaf3d8b031fc3459aa8b7f6fb8bc545 Reviewed-by: Continuous Integration (KDAB) <build@kdab.com> Reviewed-by: Brett Stottlemyer <bstottle@ford.com>
Diffstat (limited to 'src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp')
-rw-r--r--src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp867
1 files changed, 867 insertions, 0 deletions
diff --git a/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp
new file mode 100644
index 0000000..92a7a8b
--- /dev/null
+++ b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp
@@ -0,0 +1,867 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Ford Motor Company
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qremoteobjectabstractitemmodelreplica.h"
+#include "qremoteobjectabstractitemmodelreplica_p.h"
+
+#include "qremoteobjectnode.h"
+
+#include <QDebug>
+#include <QRect>
+#include <QPoint>
+
+QT_BEGIN_NAMESPACE
+
+inline QDebug operator<<(QDebug stream, const RequestedData &data)
+{
+ return stream.nospace() << "RequestedData[start=" << data.start << ", end=" << data.end << ", roles=" << data.roles << "]";
+}
+
+QAbstractItemModelReplicaPrivate::QAbstractItemModelReplicaPrivate()
+ : QRemoteObjectReplica()
+ , m_selectionModel(0)
+ , m_lastRequested(-1)
+{
+ registerTypes();
+ initializeModelConnections();
+}
+
+QAbstractItemModelReplicaPrivate::QAbstractItemModelReplicaPrivate(QRemoteObjectNode *node, const QString &name)
+ : QRemoteObjectReplica(ConstructWithNode)
+ , m_selectionModel(0)
+ , m_lastRequested(-1)
+{
+ registerTypes();
+ initializeModelConnections();
+ initializeNode(node, name);
+}
+
+QAbstractItemModelReplicaPrivate::~QAbstractItemModelReplicaPrivate()
+{
+ qDeleteAll(m_pendingRequests);
+}
+
+void QAbstractItemModelReplicaPrivate::initialize()
+{
+ QVariantList properties;
+ properties << QVariant::fromValue(QVector<int>());
+ properties << QVariant::fromValue(QIntHash());
+ setProperties(properties);
+}
+
+void QAbstractItemModelReplicaPrivate::registerTypes()
+{
+ static bool alreadyRegistered = false;
+ if (!alreadyRegistered) {
+ alreadyRegistered = true;
+ qRegisterMetaType<Qt::Orientation>();
+ qRegisterMetaType<QVector<Qt::Orientation> >();
+ qRegisterMetaTypeStreamOperators<ModelIndex>();
+ qRegisterMetaTypeStreamOperators<IndexList>();
+ qRegisterMetaTypeStreamOperators<DataEntries>();
+ qRegisterMetaTypeStreamOperators<Qt::Orientation>();
+ qRegisterMetaTypeStreamOperators<QVector<Qt::Orientation> >();
+ qRegisterMetaTypeStreamOperators<QItemSelectionModel::SelectionFlags>();
+ qRegisterMetaType<QItemSelectionModel::SelectionFlags>();
+ qRegisterMetaType<QIntHash>();
+ qRegisterMetaTypeStreamOperators<QIntHash>();
+ }
+}
+
+void QAbstractItemModelReplicaPrivate::initializeModelConnections()
+{
+ connect(this, &QAbstractItemModelReplicaPrivate::dataChanged, this, &QAbstractItemModelReplicaPrivate::onDataChanged);
+ connect(this, &QAbstractItemModelReplicaPrivate::rowsInserted, this, &QAbstractItemModelReplicaPrivate::onRowsInserted);
+ connect(this, &QAbstractItemModelReplicaPrivate::columnsInserted, this, &QAbstractItemModelReplicaPrivate::onColumnsInserted);
+ connect(this, &QAbstractItemModelReplicaPrivate::rowsRemoved, this, &QAbstractItemModelReplicaPrivate::onRowsRemoved);
+ connect(this, &QAbstractItemModelReplicaPrivate::rowsMoved, this, &QAbstractItemModelReplicaPrivate::onRowsMoved);
+ connect(this, &QAbstractItemModelReplicaPrivate::currentChanged, this, &QAbstractItemModelReplicaPrivate::onCurrentChanged);
+ connect(this, &QAbstractItemModelReplicaPrivate::modelReset, this, &QAbstractItemModelReplicaPrivate::onModelReset);
+ connect(this, &QAbstractItemModelReplicaPrivate::headerDataChanged, this, &QAbstractItemModelReplicaPrivate::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 {
+ Q_FOREACH (int role, roles) {
+ entry.data.remove(role);
+ }
+ }
+ }
+}
+
+struct OnConnectHandler
+{
+ OnConnectHandler(QAbstractItemModelReplicaPrivate *rep) : m_rep(rep){}
+ void operator()(const QModelIndex &current, const QModelIndex &/*previous*/)
+ {
+ IndexList currentIndex = toModelIndexList(current, m_rep->q);
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentIndex;
+ m_rep->replicaSetCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current);
+ }
+
+ QAbstractItemModelReplicaPrivate *m_rep;
+};
+
+void QAbstractItemModelReplicaPrivate::setModel(QAbstractItemModelReplica *model)
+{
+ q = model;
+ setParent(model);
+ m_selectionModel.reset(new QItemSelectionModel(model));
+ connect(m_selectionModel.data(), &QItemSelectionModel::currentChanged, this, OnConnectHandler(this));
+}
+
+bool QAbstractItemModelReplicaPrivate::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();
+ CacheData *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->children.count(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(row).arg(parentItem->children.count())));
+ for (int column = startColumn; column <= lastColumn; ++column) {
+ CachedRowEntry *entry = &(parentItem->children[row]->cachedRowEntry);
+ removeIndexFromRow(q->index(row, column, parentIndex), roles, entry);
+ }
+ }
+ return true;
+}
+
+void QAbstractItemModelReplicaPrivate::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
+ RequestedData data;
+ data.start = start;
+ data.end = end;
+ data.roles = roles;
+ if (clearCache(start, end, roles)) {
+ m_requestedData.append(data);
+ QMetaObject::invokeMethod(this, "fetchPendingData", Qt::QueuedConnection);
+ }
+}
+
+void QAbstractItemModelReplicaPrivate::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);
+ if (!treeFullyLazyLoaded)
+ return;
+
+ CacheData *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 QAbstractItemModelReplicaPrivate::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
+ CacheData *parentItem = cacheData(parentIndex);
+ CacheData *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 QAbstractItemModelReplicaPrivate::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;
+
+ CacheData *parentItem = cacheData(parentIndex);
+ q->beginRemoveRows(parentIndex, start, end);
+ parentItem->removeChildren(start, end);
+ q->endRemoveRows();
+}
+
+void QAbstractItemModelReplicaPrivate::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 QAbstractItemModelReplicaPrivate::onCurrentChanged(IndexList current, IndexList previous)
+{
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << current << "previous=" << previous;
+ Q_UNUSED(previous);
+ Q_ASSERT(m_selectionModel);
+ const QModelIndex currentIndex = toQModelIndex(current, q);
+ m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current);
+}
+
+void QAbstractItemModelReplicaPrivate::handleInitDone(QRemoteObjectPendingCallWatcher *watcher)
+{
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;
+
+ handleModelResetDone(watcher);
+
+ emit q->initialized();
+}
+
+void QAbstractItemModelReplicaPrivate::handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher)
+{
+ const QSize size = watcher->returnValue().value<QSize>();
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "size=" << size;
+
+ q->beginResetModel();
+ m_rootItem.clear();
+ if (size.height() > 0)
+ m_rootItem.insertChildren(0, size.height() - 1);
+ m_rootItem.columnCount = size.width();
+ m_headerData[0].resize(size.width());
+ m_headerData[1].resize(size.height());
+ q->endResetModel();
+ m_pendingRequests.removeAll(watcher);
+ delete watcher;
+}
+
+void QAbstractItemModelReplicaPrivate::handleSizeDone(QRemoteObjectPendingCallWatcher *watcher)
+{
+ SizeWatcher *sizeWatcher = static_cast<SizeWatcher*>(watcher);
+ const QSize size = sizeWatcher->returnValue().value<QSize>();
+ CacheData *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__, qPrintable(QStringLiteral("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->children.count(), __FUNCTION__, qPrintable(QStringLiteral("The new size and the current size should match!!")));
+ if (parentItem->children.isEmpty()) {
+ if (size.height() > 0) {
+ q->beginInsertRows(parent, 0, size.height() - 1);
+ parentItem->insertChildren(0, size.height() - 1);
+ q->endInsertRows();
+ }
+ } else {
+ Q_ASSERT_X(parentItem->children.count() == size.height(), __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(parentItem->children.count()).arg(size.height())));
+ }
+ m_pendingRequests.removeAll(watcher);
+ delete watcher;
+}
+
+void QAbstractItemModelReplicaPrivate::init()
+{
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;
+ SizeWatcher *watcher = doModelReset();
+ m_pendingRequests.push_back(watcher);
+ connect(watcher, &SizeWatcher::finished, this, &QAbstractItemModelReplicaPrivate::handleInitDone);
+}
+
+SizeWatcher* QAbstractItemModelReplicaPrivate::doModelReset()
+{
+ IndexList parentList;
+ QRemoteObjectPendingReply<QSize> reply = replicaSizeRequest(parentList);
+ SizeWatcher *watcher = new SizeWatcher(parentList, reply);
+ 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 QAbstractItemModelReplicaPrivate::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();
+ CacheData *parentItem = cacheData(parentList);
+ DataEntries entries = watcher->returnValue().value<DataEntries>();
+
+ const int rowCount = parentItem->children.count();
+ 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->children.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(startRow).arg(parentItem->children.size())));
+ Q_ASSERT_X(endRow >= 0 && endRow < parentItem->children.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endRow).arg(parentItem->children.size())));
+
+ for (int i = 0; i < entries.data.size(); ++i) {
+ IndexValuePair pair = entries.data[i];
+ CacheData *item = cacheData(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 QAbstractItemModelReplicaPrivate::fetchPendingData()
+{
+ if (m_requestedData.isEmpty())
+ return;
+
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "m_requestedData.size=" << m_requestedData.size();
+
+ QVector<RequestedData> finalRequests;
+ RequestedData curData;
+ Q_FOREACH (const RequestedData &data, 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())
+ Q_FOREACH (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 (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;
+ Q_FOREACH (const RequestedData &data, finalRequests) {
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "FINAL start=" << data.start << "end=" << data.end << "roles=" << data.roles;
+
+ QRemoteObjectPendingReply<DataEntries> reply = replicaRowRequest(data.start, data.end, data.roles);
+ RowWatcher *watcher = new RowWatcher(data.start, data.end, data.roles, reply);
+ m_pendingRequests.push_back(watcher);
+ connect(watcher, &RowWatcher::finished, this, &QAbstractItemModelReplicaPrivate::requestedData);
+ }
+ m_requestedData.clear();
+}
+
+void QAbstractItemModelReplicaPrivate::onModelReset()
+{
+ qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;
+ SizeWatcher *watcher = doModelReset();
+ connect(watcher, &SizeWatcher::finished, this, &QAbstractItemModelReplicaPrivate::handleModelResetDone);
+}
+
+void QAbstractItemModelReplicaPrivate::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 QAbstractItemModelReplicaPrivate::fetchPendingHeaderData()
+{
+ QVector<int> roles;
+ QVector<int> sections;
+ QVector<Qt::Orientation> orientations;
+ Q_FOREACH (const RequestedHeaderData &data, 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, &QAbstractItemModelReplicaPrivate::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 QAbstractItemModelReplicaPrivate::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];
+ QMap<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(QAbstractItemModelReplicaPrivate *rep)
+ : QAbstractItemModel()
+ , d(rep)
+{
+ rep->setModel(this);
+
+ connect(rep, &QAbstractItemModelReplicaPrivate::initialized, d.data(), &QAbstractItemModelReplicaPrivate::init);
+}
+
+QAbstractItemModelReplica::~QAbstractItemModelReplica()
+{
+}
+
+QVariant findData(const CachedRowEntry &row, const QModelIndex &index, int role, bool *cached = 0)
+{
+ if (index.column() < row.size()) {
+ const CacheEntry &entry = row[index.column()];
+ QMap<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();
+}
+
+int findCacheBorder(int start, int range, CacheData* data) {
+ const int limit = std::min(std::max(start + range, 0), data->children.size());
+ const int step = limit < start ? -1 : 1;
+ for (int i = start + step; i < limit; i += step) {
+ const CachedRowEntry &entry = data->children[i]->cachedRowEntry;
+ if (!entry.isEmpty() || i < 0 || i > data->children.size())
+ return i - step;
+ }
+ return start;
+}
+
+QItemSelectionModel* QAbstractItemModelReplica::selectionModel() const
+{
+ return d->m_selectionModel.data();
+}
+
+bool QAbstractItemModelReplica::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ 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();
+
+ CacheData *item = d->cacheData(index);
+ CacheData *parentItem = item->parent;
+ Q_ASSERT(parentItem);
+ Q_ASSERT(parentItem->children.indexOf(item) >= 0);
+ Q_ASSERT(index.row() < parentItem->children.count());
+
+ bool cached = false;
+ const CachedRowEntry &entry = item->cachedRowEntry;
+ QVariant result = findData(entry, index, role, &cached);
+ if (!cached) {
+ const int row = index.row();
+ const int low = findCacheBorder(row, -HalfLookAhead, parentItem);
+ const int high = findCacheBorder(row, HalfLookAhead, parentItem);
+ Q_ASSERT_X(low >= 0 && low < parentItem->children.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(low).arg(parentItem->children.size())));
+ Q_ASSERT_X(high >= 0 && high < parentItem->children.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(high).arg(parentItem->children.size())));
+
+ IndexList parentList = toModelIndexList(index.parent(), this);
+ IndexList start = IndexList() << parentList << ModelIndex(low, 0);
+ IndexList end = IndexList() << parentList << ModelIndex(high, 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 result;
+}
+QModelIndex QAbstractItemModelReplica::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+ CacheData* item = d->cacheData(index);
+ Q_ASSERT(item != &d->m_rootItem);
+ Q_ASSERT(item->parent);
+ if (item->parent == &d->m_rootItem)
+ return QModelIndex();
+ Q_ASSERT(item->parent->parent);
+ int row = item->parent->parent->children.indexOf(item->parent);
+ Q_ASSERT(row >= 0);
+ return createIndex(row, 0, item->parent);
+}
+QModelIndex QAbstractItemModelReplica::index(int row, int column, const QModelIndex &parent) const
+{
+ CacheData *parentItem = d->cacheData(parent);
+ Q_ASSERT_X((parent.isValid() && 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->children.count())
+ return QModelIndex();
+ if (column < 0 || column >= parentItem->columnCount)
+ return QModelIndex();
+
+ Q_ASSERT_X(row >= 0 && row < parentItem->children.count(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(row).arg(parentItem->children.count())));
+
+ return createIndex(row, column, parentItem->children[row]);
+}
+bool QAbstractItemModelReplica::hasChildren(const QModelIndex &parent) const
+{
+ CacheData *parentItem = d->cacheData(parent);
+ if (parent.isValid() && parent.column() != 0)
+ return false;
+ else
+ return parentItem->hasChildren;
+}
+int QAbstractItemModelReplica::rowCount(const QModelIndex &parent) const
+{
+ CacheData *parentItem = d->cacheData(parent);
+ const bool canHaveChildren = parentItem->hasChildren && parentItem->children.isEmpty() && 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(), &QAbstractItemModelReplicaPrivate::handleSizeDone);
+ } else if (parent.column() > 0)
+ return 0;
+ return parentItem->children.count();
+}
+int QAbstractItemModelReplica::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid() && parent.column() > 0)
+ return 0;
+ CacheData *parentItem = d->cacheData(parent);
+ 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 QMap<int, QVariant> &dat = elem.at(section).data;
+ QMap<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;
+ CacheData *item = d->cacheData(index);
+ bool cached = false;
+ const CachedRowEntry &entry = item->cachedRowEntry;
+ QVariant result = findData(entry, index, role, &cached);
+ Q_UNUSED(result);
+ return cached;
+}
+
+QVector<int> QAbstractItemModelReplica::availableRoles() const
+{
+ return d->availableRoles();
+}
+
+QHash<int, QByteArray> QAbstractItemModelReplica::roleNames() const
+{
+ return d->roleNames();
+}
+
+QT_END_NAMESPACE