/* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kreparentingproxymodel.h" #include #include #include #include class KReparentingProxyModelPrivate { KReparentingProxyModelPrivate(KReparentingProxyModel *proxyModel) : m_nextId(0) , q_ptr(proxyModel) { } qint64 newId() const { return m_nextId++; } enum MapStrategy { MapDescendants, MapChildrenOnly, }; /** Creates mappings of indexes in the source model between @p start and @p end which should be represented in the proxy model as descendants of @p parent. */ QHash recreateMappings(const QModelIndex &parent, int start, int end = -1, int strategy = MapChildrenOnly) const; /** Merges all indexes from @p mappings which are descendants of @p parent into the model. Returns the remaining mappings. Note that this changes the internal model structure and must only be called between begin/end insert/remove/move/reset calls. */ QHash mergeDescendants(QHash mappings, const QModelIndex &parent, int start); /** Verifies that the indexes below @p parent between @p start and rowCount(parent) are in the correct positions in the proxy model. Repositions them if not. */ void verifyStructure(const QModelIndex &parent, int start); /** Returns the index vertically below index in the model @p model. If @p model is 0, the sourceModel is used Returns an invalid index if there is no index below @p index. */ QModelIndex getIndexBelow(const QModelIndex &index, QAbstractItemModel *model = nullptr) const; /** Returns the last descendant of @p index or itself if it has no children */ QModelIndex getLastDescendant(const QModelIndex &index) const; bool isDescendantInModel(const QModelIndex &ancestor, const QModelIndex &descendant) const; /** Returns the ancestors of @p descendant that are already in the proxy model. Note that @p descendant does not have to be in the proxy yet, and it is not part of the result list. */ QList getExistingAncestors(const QModelIndex &descendant) const; void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end); void sourceRowsInserted(const QModelIndex &parent, int start, int end); void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void sourceRowsRemoved(const QModelIndex &parent, int start, int end); void sourceRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow); void sourceRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow); void sourceModelAboutToBeReset(); void endResetProxy(); void sourceModelReset(); void sourceLayoutAboutToBeChanged(); void sourceLayoutChanged(); void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); mutable QHash m_parents; mutable QHash> m_childIndexes; struct PendingInsertion { PendingInsertion() : parentId(-1) , start(-1) , end(-1) { } PendingInsertion(const QModelIndex &_index, int _start, int _end) : index(_index) , start(_start) , end(_end) { } QPersistentModelIndex index; QModelIndex sourceIndex; qint64 parentId; int start; int end; }; struct PendingRemoval : PendingInsertion { int numTrailing; }; // Needed between the beginRemoveRows and endRemoveRows signals. mutable QHash m_pendingRemovalParents; mutable QHash> m_pendingRemovalChildIndexes; QHash insertTree(QHash mappings, const QModelIndex &parent); void handleInsertion(const PendingInsertion &pendingInsertion); void handleRemoval(const PendingRemoval &pendingRemoval); mutable QHash m_pendingInsertions; mutable QList m_pendingRemovals; mutable qint64 m_nextId; Q_DECLARE_PUBLIC(KReparentingProxyModel) KReparentingProxyModel *q_ptr; QList m_layoutChangePersistentIndexes; QModelIndexList m_proxyIndexes; void emitDataChangedSignals(const QModelIndex &parent, int maxChanged); /** Given @p parent in the proxy model, return the last index lying between @p start and @p end which is also a descendant of @p parent. */ QModelIndex findLastInParent(QModelIndex parent, int start, int end); /** Removes @P idx (which is a source model index) and its children from the model data structures. */ void removeTree(const QPersistentModelIndex &idx, int start = 0, int end = -1); int pendingRemovalRowCount(const QModelIndex &sourceIndex) const; }; class LessThan { const KReparentingProxyModel *m_model; public: LessThan(const KReparentingProxyModel *model) : m_model(model) { } bool operator()(const QModelIndex &ancestor, const QModelIndex &descendant) { return m_model->isDescendantOf(ancestor, descendant); } }; QModelIndex KReparentingProxyModelPrivate::getIndexBelow(const QModelIndex &index, QAbstractItemModel *model) const { Q_Q(const KReparentingProxyModel); // qDebug() << index.data() << index; if (!model) { model = q->sourceModel(); } if (model->hasChildren(index)) { return model->index(0, 0, index); } QModelIndex sibling = index.sibling(index.row() + 1, index.column()); if (sibling.isValid()) { return sibling; } QModelIndex parent = index.parent(); if (!parent.isValid()) { return QModelIndex(); } int affectedRow = index.row(); const int column = 0; while (parent.isValid()) { // qDebug() << "parent" << parent.data() << model->rowCount(parent) << affectedRow; if (affectedRow < model->rowCount(parent) - 1) { return model->index(affectedRow + 1, column, parent); } affectedRow = parent.row(); parent = parent.parent(); } if (model->rowCount(parent) >= affectedRow) { return model->index(affectedRow + 1, column, parent); } return QModelIndex(); } QModelIndex KReparentingProxyModelPrivate::getLastDescendant(const QModelIndex &index) const { Q_Q(const KReparentingProxyModel); QModelIndex proxyIndex = q->mapFromSource(index); while (q->hasChildren(proxyIndex)) { proxyIndex = q->index(q->rowCount(proxyIndex), proxyIndex.column(), proxyIndex); if (!proxyIndex.isValid()) { break; } } return q->mapToSource(proxyIndex); } QList KReparentingProxyModelPrivate::getExistingAncestors(const QModelIndex &descendant) const { Q_Q(const KReparentingProxyModel); QList vector; if (!descendant.isValid()) { return vector; } QModelIndex parent = q->mapFromSource(descendant).parent(); QModelIndex sourceParent = q->mapToSource(parent); if (!sourceParent.isValid()) { return vector; } vector.append(sourceParent); while (parent.isValid()) { parent = parent.parent(); sourceParent = q->mapToSource(parent); if (!sourceParent.isValid()) { return vector; } vector.prepend(sourceParent); } return vector; } QHash KReparentingProxyModelPrivate::recreateMappings(const QModelIndex &ancestor, int start, int end, int strategy) const { Q_Q(const KReparentingProxyModel); const int column = 0; QHash mappings; // Handle listing the root QModelIndex(). if (!ancestor.isValid() && !q->sourceModel()->hasChildren()) // Empty model. Nothing to do. { return mappings; } // A // - B // - - C // - D // If start refers to D, existing ancestors will contain only A. // We need to go 'up' to C and get its ancestors in case D is to be made a child of B or C (for example if B and C have just been inserted) QModelIndex indexAbove; if (start > 0) { indexAbove = getLastDescendant(q->sourceModel()->index(start - 1, column, ancestor)); } else { indexAbove = ancestor; } QList ancestors = getExistingAncestors(indexAbove); // ancestors.append(indexAbove); // qDebug() << ancestors; QModelIndex nextIndex = ancestor; for (int row = start; (row <= end || end == -1);) { // A // - B // - - C // - D // The nextIndex of the invalid QModelIndex is A, // The nextIndex of A is B, // The nextIndex of B is C, // The nextIndex of C is D, // The nextIndex of D is invalid, // When the nextIndex is invalid we're finished creating mappings. if (MapDescendants == strategy) { nextIndex = getIndexBelow(nextIndex); } else { nextIndex = q->sourceModel()->index(row, column, ancestor); } if (!nextIndex.isValid()) { break; } const QList::iterator ancestorIt = std::lower_bound(ancestors.begin(), ancestors.end(), nextIndex, LessThan(q)); ancestors.erase(ancestorIt, ancestors.end()); QModelIndex parent; if (ancestorIt != ancestors.begin()) { parent = *(ancestorIt - 1); } ancestors.append(nextIndex); mappings[parent].append(nextIndex); } return mappings; } void KReparentingProxyModelPrivate::verifyStructure(const QModelIndex &sourceParent, int sourceStart) { Q_Q(KReparentingProxyModel); // If the start structure is: // C // D // E // and then A and B are inserted, we may need to move C D and E. Not all of the siblings will // necessarily be moved to the same destination parent. // Some example finished scenarios depending on the outcome of isDescendantOf: // A // B // C // D // E // A // B // - C // - D // - E // A // - B // - C // - D // - E // A // - B // - - C // - D // E // Local variable mappings now contains all the information about finished state // When we locate the first child to be moved, we process it and its siblings QHash mappings = recreateMappings(sourceParent, sourceStart, -1); if (mappings.isEmpty()) { return; } QModelIndex sourceFirstIndex = q->sourceModel()->index(sourceStart, 0, sourceParent); QModelIndex destinationParent; QModelIndexList movedIndexes; QHashIterator it(mappings); while (it.hasNext()) { it.next(); // qDebug() << it.key() << it.key().data() << it.value(); if (it.value().at(0) == sourceFirstIndex) { destinationParent = it.key(); movedIndexes = it.value(); break; } } Q_FOREVER { if (destinationParent == sourceParent) // No indexes moved { return; } Q_ASSERT(destinationParent.isValid()); Q_ASSERT(!movedIndexes.isEmpty()); // It's only possible for things to move right, and even that's only an option // for children of parent, but not their descendants. ie, children of C D and E will not need to be reparented. // They are already in the correct positions. QList &existingSourceIndexes = m_childIndexes[sourceParent]; QList existingDestinationIndexes = m_childIndexes[destinationParent]; QModelIndex proxySourceParent = q->mapFromSource(sourceParent); QModelIndex proxyDestinationParent = q->mapFromSource(destinationParent); // That is, start position of indexes to be moved from the source parent. int proxySourceStart = m_childIndexes.value(sourceParent).indexOf(movedIndexes.at(0)); int proxySourceEnd = proxySourceStart + movedIndexes.size() - 1; // The moved indexes are appended to the destinationParent. Nothing else is possible. // If they were to be inserted in the middle somewhere, they would already be there. int destinationRow = existingDestinationIndexes.size(); bool allowMove = q->beginMoveRows(proxySourceParent, proxySourceStart, proxySourceEnd, proxyDestinationParent, destinationRow); Q_ASSERT(allowMove); for (int row = proxySourceEnd; row >= proxySourceStart; --row) { existingSourceIndexes.removeAt(row); } QHash mapping; mapping.insert(destinationParent, movedIndexes); mergeDescendants(mapping, destinationParent, existingDestinationIndexes.size()); q->endMoveRows(); if (!mappings.contains(q->mapToSource(proxyDestinationParent.parent()))) { break; } destinationParent = q->mapToSource(proxyDestinationParent.parent()); movedIndexes = mappings.value(destinationParent); } } KReparentingProxyModel::KReparentingProxyModel(QObject *parent) : QAbstractProxyModel(parent) , d_ptr(new KReparentingProxyModelPrivate(this)) { } KReparentingProxyModel::~KReparentingProxyModel() { delete d_ptr; } bool KReparentingProxyModelPrivate::isDescendantInModel(const QModelIndex &ancestor, const QModelIndex &descendant) const { // qDebug() << ancestor.data() << descendant.data(); // if (!ancestor.isValid()) // return true; QModelIndex _ancestor = descendant.parent(); while (_ancestor.isValid()) { if (_ancestor == ancestor) { return true; } _ancestor = _ancestor.parent(); } return (!ancestor.isValid() && descendant.isValid()); } bool KReparentingProxyModel::isDescendantOf(const QModelIndex &ancestor, const QModelIndex &descendant) const { Q_D(const KReparentingProxyModel); return d->isDescendantInModel(ancestor, descendant); // return (!ancestor.isValid() && descendant.isValid()); } QModelIndex KReparentingProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { Q_D(const KReparentingProxyModel); if (!sourceIndex.isValid()) { return QModelIndex(); } QModelIndex sourceIndexFirstColumn = sourceIndex.sibling(sourceIndex.row(), 0); QHash>::const_iterator it; const QHash>::const_iterator begin = d->m_childIndexes.constBegin(); const QHash>::const_iterator end = d->m_childIndexes.constEnd(); for (it = begin; it != end; ++it) { QList list = it.value(); if (list.contains(sourceIndexFirstColumn)) { QModelIndex sourceParent = it.key(); int row = list.indexOf(sourceIndexFirstColumn); // There must have been a mapping made for it. Q_ASSERT(d->m_parents.values().contains(sourceParent)); qint64 id = d->m_parents.key(sourceParent); // id refers to the parent. return createIndex(row, sourceIndex.column(), reinterpret_cast(id)); } } return QModelIndex(); } QModelIndex KReparentingProxyModel::mapToSource(const QModelIndex &proxyIndex) const { Q_D(const KReparentingProxyModel); // qDebug() << "MMMMMM" << proxyIndex; if (!proxyIndex.isValid()) { return QModelIndex(); } qint64 id = reinterpret_cast(proxyIndex.internalPointer()); // if (!d->m_parents.contains(id)) // qDebug() << d->m_parents << id; QModelIndex sourceParent; if (d->m_pendingRemovalParents.contains(id)) { // qDebug() << "pending"; sourceParent = d->m_pendingRemovalParents.value(id); } else { Q_ASSERT(d->m_parents.contains(id)); sourceParent = d->m_parents.value(id); } // qDebug() << sourceParent << sourceParent.data(); QModelIndex sourceIndexFirstColumn; if (d->m_pendingRemovalChildIndexes.contains(sourceParent)) { // qDebug() << "#############"; for (const KReparentingProxyModelPrivate::PendingRemoval &pendingRemoval : std::as_const(d->m_pendingRemovals)) { // qDebug() << "In" << pendingRemoval.index << pendingRemoval.sourceIndex << sourceParent; if (pendingRemoval.sourceIndex == sourceParent) { // qDebug() << "Out" << pendingRemoval.sourceIndex << sourceParent; int proxyRow = proxyIndex.row(); int row = proxyRow - pendingRemoval.start; // qDebug() << d->m_pendingRemovalChildIndexes.value(sourceParent) << proxyRow << row << pendingRemoval.end; if (proxyRow > pendingRemoval.end) { Q_ASSERT(d->m_childIndexes.contains(sourceParent)); row = proxyRow - (pendingRemoval.end - pendingRemoval.start + 1); // qDebug() << "new row" << row; sourceIndexFirstColumn = d->m_childIndexes.value(sourceParent).at(row); } else { sourceIndexFirstColumn = d->m_pendingRemovalChildIndexes.value(sourceParent).at(row); } break; } } } else { Q_ASSERT(d->m_childIndexes.contains(sourceParent)); sourceIndexFirstColumn = d->m_childIndexes.value(sourceParent).at(proxyIndex.row()); } Q_ASSERT(sourceIndexFirstColumn.isValid()); return sourceIndexFirstColumn.sibling(sourceIndexFirstColumn.row(), proxyIndex.column()); } int KReparentingProxyModel::columnCount(const QModelIndex &parent) const { Q_D(const KReparentingProxyModel); if (!sourceModel()) { return 0; } if (!parent.isValid()) { return sourceModel()->columnCount(); } if (parent.column() > 0) { return 0; } QModelIndex sourceIndex = mapToSource(parent); return (d->m_childIndexes.value(sourceIndex).size() > 0) ? sourceModel()->columnCount() : 0; } QVariant KReparentingProxyModel::data(const QModelIndex &proxyIndex, int role) const { return QAbstractProxyModel::data(proxyIndex, role); } QModelIndex KReparentingProxyModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const KReparentingProxyModel); if (!hasIndex(row, column, parent)) { return QModelIndex(); } QModelIndex sourceParent = mapToSource(parent); // if (!d->m_pendingRemovals.isEmpty()) // qDebug() << sourceParent << sourceParent.data(); // ### This is where we need to have the children of removed indexes stored. // if (!d->m_parents.values().contains(sourceParent)) // { // qDebug() << d->m_pendingRemovalParents.values() << sourceParent << d->m_pendingRemovalParents.values().contains(sourceParent); // } qint64 id; if (d->m_pendingRemovalParents.values().contains(sourceParent)) { id = d->m_pendingRemovalParents.key(sourceParent); } else { // There must have been a mapping made for it. Q_ASSERT(d->m_parents.values().contains(sourceParent)); id = d->m_parents.key(sourceParent); } return createIndex(row, column, reinterpret_cast(id)); } QModelIndex KReparentingProxyModel::parent(const QModelIndex &child) const { Q_D(const KReparentingProxyModel); if (!child.isValid()) { return QModelIndex(); } QModelIndex sourceIndex = mapToSource(child); QModelIndex firstColumnChild = sourceIndex; if (sourceIndex.column() > 0) { firstColumnChild = sourceIndex.sibling(sourceIndex.row(), 0); } QHashIterator> itPending(d->m_pendingRemovalChildIndexes); while (itPending.hasNext()) { itPending.next(); if (itPending.value().contains(firstColumnChild)) { return mapFromSource(itPending.key()); } } QHashIterator> it(d->m_childIndexes); while (it.hasNext()) { it.next(); if (it.value().contains(firstColumnChild)) { return mapFromSource(it.key()); } } return QModelIndex(); } int KReparentingProxyModelPrivate::pendingRemovalRowCount(const QModelIndex &sourceIndex) const { for (const PendingRemoval &pendingRemoval : std::as_const(m_pendingRemovals)) { // qDebug() << pendingRemoval.sourceIndex; if (pendingRemoval.sourceIndex == sourceIndex) { return pendingRemoval.end - pendingRemoval.start + 1; } } return 0; } int KReparentingProxyModel::rowCount(const QModelIndex &parent) const { Q_D(const KReparentingProxyModel); if (parent.column() > 0) { return 0; } QModelIndex sourceIndex = mapToSource(parent); int size = d->m_childIndexes.value(sourceIndex).size() + d->m_pendingRemovalChildIndexes.value(sourceIndex).size(); // qDebug() << d->m_pendingRemovalChildIndexes.value(sourceIndex).size(); // if (!d->m_pendingRemovals.isEmpty()) // { // qDebug() << "SIZE" << sourceIndex << sourceIndex.data() << size << d->m_pendingRemovals.size() << d->pendingRemovalRowCount(sourceIndex); // } return size; } bool KReparentingProxyModel::hasChildren(const QModelIndex &parent) const { return rowCount(parent) > 0; } void KReparentingProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { Q_D(KReparentingProxyModel); beginResetModel(); disconnect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int))); disconnect(sourceModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(sourceRowsInserted(QModelIndex, int, int))); disconnect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int))); disconnect(sourceModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsRemoved(QModelIndex, int, int))); disconnect(sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(sourceRowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int))); disconnect(sourceModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(sourceRowsMoved(QModelIndex, int, int, QModelIndex, int))); disconnect(sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset())); disconnect(sourceModel, SIGNAL(modelReset()), this, SLOT(sourceModelReset())); disconnect(sourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex, QModelIndex))); disconnect(sourceModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); disconnect(sourceModel, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged())); QAbstractProxyModel::setSourceModel(sourceModel); // qDebug() << "set"; QHash mappings = d->recreateMappings(QModelIndex(), 0, sourceModel->rowCount() - 1, KReparentingProxyModelPrivate::MapDescendants); // qDebug() << "begin"; d->mergeDescendants(mappings, QModelIndex(), 0); connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int))); connect(sourceModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(sourceRowsInserted(QModelIndex, int, int))); connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int))); connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(sourceRowsRemoved(QModelIndex, int, int))); connect(sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(sourceRowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int))); connect(sourceModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(sourceRowsMoved(QModelIndex, int, int, QModelIndex, int))); connect(sourceModel, SIGNAL(modelAboutToBeReset()), SLOT(sourceModelAboutToBeReset())); connect(sourceModel, SIGNAL(modelReset()), SLOT(sourceModelReset())); connect(sourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(sourceDataChanged(QModelIndex, QModelIndex))); connect(sourceModel, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); connect(sourceModel, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged())); endResetModel(); } void KReparentingProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); Q_Q(KReparentingProxyModel); return q->beginResetModel(); #if 0 // We can't figure out the structure until the indexes are in the model. // Store the signal until the new rows are actually there in sourceRowsInserted. PendingInsertion pendingInsertion(parent, start, end); m_pendingInsertions.insert(parent, pendingInsertion); #endif } QHash KReparentingProxyModelPrivate::mergeDescendants(QHash mappings, const QModelIndex &parent, int start) { const QModelIndexList childIndexes = mappings.take(parent); // qDebug() << childIndexes; if (!childIndexes.isEmpty()) { if (!m_parents.values().contains(parent)) { m_parents.insert(newId(), QPersistentModelIndex(parent)); } } int row = start; for (const QModelIndex &idx : childIndexes) { m_childIndexes[parent].insert(row++, QPersistentModelIndex(idx)); mappings = mergeDescendants(mappings, idx, 0); } return mappings; } QHash KReparentingProxyModelPrivate::insertTree(QHash, const QModelIndex &) { return QHash(); } void KReparentingProxyModelPrivate::handleInsertion(const PendingInsertion &pendingInsertion) { Q_Q(KReparentingProxyModel); QModelIndex parent = pendingInsertion.index; int start = pendingInsertion.start; int end = pendingInsertion.end; // qDebug() << parent << parent.data() << start << end; // for (int i = start; i < end; ++i) // { // QModelIndex idx = q->sourceModel()->index(i, 0, parent); // qDebug() << idx << idx.data(); // } QHash newItemMappings = recreateMappings(parent, start, end, KReparentingProxyModelPrivate::MapDescendants); // iterate over keys. if key in keys iterate up. This gives list of top level parents. // Pick the one whose parent is @p parent. Insert it. Look up until find the parent of another one and insert that. // If one of the parents is invalid it is necessarily the last one to be processed (if there are more to process, they'll be children of it) // That case should work too. // qDebug() << "new item mappings" << newItemMappings; const int column = 0; // qDebug() << m_childIndexes.contains(parent); if (newItemMappings.contains(parent)) { QModelIndexList newItemList = newItemMappings.value(parent); // qDebug() << "newItemList" << newItemList; int proxyStart = 0; // A single insertion in the source model might be multiple insertions in the proxy model. Q_FOREVER { if (newItemList.isEmpty()) { if (!newItemMappings.contains(parent.parent())) { break; } newItemList = newItemMappings.value(parent.parent()); continue; } proxyStart = 0; QModelIndex proxyParent = q->mapFromSource(parent); if (start > 0) { QModelIndex lastDesc = q->mapFromSource(getLastDescendant(q->sourceModel()->index(start - 1, column, parent))); while (lastDesc.parent() != proxyParent) { lastDesc = lastDesc.parent(); } proxyStart = lastDesc.row() + 1; } q->beginInsertRows(proxyParent, proxyStart, proxyStart + newItemList.size() - 1); newItemMappings = mergeDescendants(newItemMappings, parent, proxyStart); q->endInsertRows(); if (!newItemMappings.contains(parent.parent())) { break; } newItemList = newItemMappings.value(parent.parent()); } } // // The rest are not descendants of pendingInsertion.index in the proxy model, but are elsewhere. // Q_FOREACH(const QModelIndex &parent, newItemMappings.keys()) // { // // } return; } void KReparentingProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int, int end) { Q_UNUSED(parent); Q_UNUSED(end); return endResetProxy(); #if 0 Q_Q(KReparentingProxyModel); if (m_pendingInsertions.contains(parent)) { PendingInsertion pendingInsertion = m_pendingInsertions.value(parent); handleInsertion(pendingInsertion); if (q->sourceModel()->rowCount(parent) <= (end + 1)) { return; } // The presence of new rows might affect the structure of indexes below. verifyStructure(parent, end + 1); } #endif } void KReparentingProxyModelPrivate::removeTree(const QPersistentModelIndex &idxToRemove, int start, int end) { if (!m_childIndexes.contains(idxToRemove)) { return; } // qDebug() << "idxToRemove" << idxToRemove << start << end; QList &toRemove = m_childIndexes[idxToRemove]; // qDebug() << toRemove << toRemove.size(); // QList intList; // intList << 1 << 2 << 3 << 4 << 5; // // QList::iterator intit = intList.begin(); // QList::iterator intendIt = intList.end(); // // if (end == 0) // intendIt = intit + 1; // // if (end > 0) // { // intendIt = intit + (end - start + 1) + 1; // qDebug() << "intend" << *intendIt; // } // intit += start; // // while (intit != intendIt) // { // int i = *intit; // qDebug() << i; // intit = intList.erase(intit); // } QList::iterator it = toRemove.begin(); QList::iterator endIt = toRemove.end(); if (end == 0) { endIt = it + 1; } if (end > 0) { endIt = it + (end - start + 1) + 1; } it += start; int i = start; while (it != endIt) { QPersistentModelIndex idx = *it; // qDebug() << "removing" << idx << idx.data(); if (m_parents.values().contains(idx)) { qint64 key = m_parents.key(idx); QPersistentModelIndex value = m_parents.take(key); m_pendingRemovalParents.insert(key, value); // qDebug() << "take from parent" << value; } removeTree(idx); ++i; m_pendingRemovalChildIndexes[idxToRemove].append(idx); // qDebug() << idxToRemove << idxToRemove.data() << idx << idx.data(); it = toRemove.erase(it); // qDebug() << (it == endIt); // if (i > end) // break; // if (it == toRemove.end()) // break; } // qDebug() << "toRemove" << toRemove; // for(int i = start; (i <= end || (end == -1 && toRemove.size() > i)); ) // { // qDebug() << i; // QPersistentModelIndex idx = toRemove.takeAt(i); // --end; // // qDebug() << "removing" << idx.data(); // // if (m_parents.values().contains(idx)) // { // QPersistentModelIndex bah = m_parents.take(m_parents.key(idx)); // // qDebug() << "take from parent" << bah; // } // removeTree(idx); // } } void KReparentingProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); Q_Q(KReparentingProxyModel); q->beginResetModel(); return; #if 0 // qDebug() << parent << start << end; // This is really tricky. // // We could have something like: // // A A // B - B // C -> - - C // D D // E - E // // And have to remove something like B to D. That would mean a remove signal for B, move E to its grandparent, remove D. // QHashIterator > it(m_childIndexes); // while (it.hasNext()) // { // it.next(); // qDebug() << it.key() << it.key().data(); // qDebug() << it.value(); // } const int column = 0; QModelIndex firstAffectedIndex = q->mapFromSource(q->sourceModel()->index(start, column, parent)); QModelIndex lastAffectedIndex = q->mapFromSource(q->sourceModel()->index(end, column, parent)); // qDebug() << "firstAffectedIndex" << firstAffectedIndex.data(); // qDebug() << "lastAffectedIndex" << lastAffectedIndex.data(); QModelIndex proxyParent = firstAffectedIndex.parent(); Q_ASSERT(firstAffectedIndex.isValid() && lastAffectedIndex.isValid()); Q_FOREVER { if (isDescendantInModel(proxyParent, lastAffectedIndex)) { // They share a common ancestor. QModelIndex _parent = lastAffectedIndex.parent(); QModelIndex lastAffectedAncestor = lastAffectedIndex; // qDebug() << "last affected ancestor" << lastAffectedAncestor.data(); while (_parent != proxyParent) { lastAffectedAncestor = _parent; _parent = _parent.parent(); } if (q->hasChildren(lastAffectedAncestor)) { QModelIndex next = q->index(0, 0, lastAffectedAncestor); QModelIndex proxySourceParent = lastAffectedAncestor; int startRow = next.row(); int lastRow = q->rowCount(lastAffectedAncestor) - 1; QList &existingSourceIndexes = m_childIndexes[q->mapToSource(proxySourceParent)]; QList &existingDestinationIndexes = m_childIndexes[q->mapToSource(proxyParent)]; int destRow = lastAffectedAncestor.row() + 1; // qDebug() << "Move from" << lastAffectedAncestor.data() << startRow << lastRow << " To " << proxyParent.data() << destRow; bool allowMove = q->beginMoveRows(lastAffectedAncestor, startRow, lastRow, proxyParent, destRow); Q_ASSERT(allowMove); for (int i = startRow; i <= lastRow; ++i) { QPersistentModelIndex movingIdx = existingSourceIndexes.takeAt(startRow); existingDestinationIndexes.insert(destRow + (i - startRow), movingIdx); } // TODO: If source was a parent before, it might not be now. // dest was already a parent. q->endMoveRows(); } PendingRemoval removal; removal.index = proxyParent; removal.start = firstAffectedIndex.row(); removal.end = lastAffectedAncestor.row(); removal.parentId = proxyParent.internalId(); removal.sourceIndex = q->mapToSource(proxyParent); m_pendingRemovals.append(removal); removeTree(q->mapToSource(proxyParent), removal.start, removal.end); // qDebug() << "beg rem 1"; q->beginRemoveRows(proxyParent, removal.start, removal.end); return; } else { QModelIndex next = getIndexBelow(firstAffectedIndex); proxyParent = next.parent(); while (isDescendantInModel(proxyParent, next)) { next = getIndexBelow(next); } QModelIndex _parent = next.parent(); QModelIndex lastAffectedAncestor = next; while (_parent != proxyParent) { lastAffectedAncestor = _parent; _parent = _parent.parent(); } PendingRemoval removal; removal.index = proxyParent; removal.start = firstAffectedIndex.row(); removal.end = lastAffectedAncestor.row(); removal.parentId = proxyParent.internalId(); removal.sourceIndex = q->mapToSource(proxyParent); m_pendingRemovals.append(removal); removeTree(q->mapToSource(proxyParent), removal.start, removal.end); // qDebug() << "beg rem 1"; q->beginRemoveRows(proxyParent, removal.start, removal.end); proxyParent = next.parent(); } } // // qDebug() << proxyParent.data() << lastAffectedIndex.parent().data() << proxyParent << lastAffectedIndex.parent(); // if (proxyParent == lastAffectedIndex.parent()) // { // PendingRemoval removal; // removal.index = proxyParent; // removal.start = firstAffectedIndex.row(); // removal.end = lastAffectedIndex.row(); // removal.parentId = proxyParent.internalId(); // removal.sourceIndex = q->mapToSource(proxyParent); // m_pendingRemovals.append(removal); // // // Also need to store a removal object for each of the descendants. // // removeTree(q->mapToSource(proxyParent), removal.start, removal.end); // // // qDebug() << "beg rem 1"; // q->beginRemoveRows(proxyParent, removal.start, removal.end); // return; // } // // QModelIndex lastParent = lastAffectedIndex.parent(); // while (lastParent.parent().isValid()) // { // if (lastParent.parent() == proxyParent) // { // PendingRemoval removal; // removal.index = proxyParent; // removal.start = firstAffectedIndex.row(); // removal.end = lastParent.row(); // removal.parentId = proxyParent.internalId(); // removal.sourceIndex = q->mapToSource(proxyParent); // m_pendingRemovals.append(removal); // // // qDebug() << "beg rem 2"; // q->beginRemoveRows(proxyParent, removal.start, removal.end); // return; // } // lastParent = lastParent.parent(); // } // // // Several blocks need to be removed from the proxy model. // // Divide and conquer to find them. // // int proxyStart = firstAffectedIndex.row(); // int proxyEnd = proxyStart + (end - start); // int processedUntil = start; // // while (processedUntil <= end) // { // QModelIndex lastInParent = findLastInParent(proxyParent, proxyStart, proxyEnd); // qDebug() << "lastInParent" << lastInParent; // // QModelIndex sourceLast = q->mapToSource(lastInParent); // processedUntil = sourceLast.row(); // // PendingRemoval removal; // removal.index = proxyParent; // removal.start = proxyStart; // removal.end = lastInParent.row(); // removal.parentId = proxyParent.internalId(); // removal.sourceIndex = q->mapToSource(proxyParent); // m_pendingRemovals.append(removal); // // qDebug() << "beg rem 3"; // q->beginRemoveRows(proxyParent, removal.start, removal.end); // // QModelIndex proxyIndexBelow = getIndexBelow(lastInParent, q); // // if (!proxyIndexBelow.isValid()) // return; // // proxyParent = proxyIndexBelow.parent(); // proxyStart = proxyIndexBelow.row(); // } #endif } QModelIndex KReparentingProxyModelPrivate::findLastInParent(QModelIndex parent, int start, int end) { Q_Q(KReparentingProxyModel); const int column = 0; if (start == end) { return q->index(start, column, parent); } int middle = start + (end - start / 2); QModelIndex sourceParent = q->mapToSource(parent); QModelIndex middleIndex = q->mapFromSource(q->sourceModel()->index(middle, column, sourceParent)); if (middleIndex.parent() == parent) { return findLastInParent(parent, middle, end); } else { return findLastInParent(parent, start + ((middle - start) / 2), middle); } } // qDebug() << affectedIndex << affectedIndex.data() << proxyParent; // // QHash pendingRemovals; // // int i = start; // while (i <= end) // { // affectedIndex = affectedIndex.sibling(i, column); // // // affectedIndex = getIndexBelow(affectedIndex, q); // if (!affectedIndex.isValid()) // break; // // Q_ASSERT(affectedIndex.isValid()); // // if (affectedIndex.parent() != proxyParent) // { // // affectedIndex.parent() must be left of proxyParent // // PendingRemoval removal; // removal.index = proxyParent; // removal.start = start; // removal.end = i; // pendingRemovals.insert(proxyParent, removal); // // Q_EMIT q->rowsAboutToBeRemoved(proxyParent, start, i); // proxyParent = affectedIndex.parent(); // // end -= (i - start + 1); // start = affectedIndex.row(); // i = start; // } // // ++i; // } // Move younger siblings out of the way so that the rows can be removed easily // No. It's easier to use verifyStructure afterward. // // Removing rows in the source model could require sending the children to their grandparents. // // QHash mappings; // recreateMappings(parent, start, end); // // QHashIterator it(mappings); // while (it.hasNext()) // { // it.next(); // QModelIndexList removedList = it.value(); // PendingRemoval pendingRemoval; // pendingRemoval.index = it.key(); // pendingRemoval.start = q->mapFromSource(removedList.at(0)).row(); // pendingRemoval.end = pendingRemoval.start + removedList.size() - 1; // m_pendingRemovals.insert(parent, pendingRemoval); // } // } void KReparentingProxyModelPrivate::handleRemoval(const PendingRemoval &pendingRemoval) { Q_UNUSED(pendingRemoval) // Q_Q(KReparentingProxyModel); // q->beginRemoveRows(pendingRemoval.index, pendingRemoval.start, pendingRemoval.end); // m_childIndexes.remove(pendingRemoval.index); // // Remove stuff from m_parents. // q->endRemoveRows(); } void KReparentingProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int, int) { return endResetProxy(); Q_Q(KReparentingProxyModel); // loop over pending removals and process each one. Then look after the last one // to move displaced rows to where they should be. int lastAffectedRow = m_pendingRemovals.last().end; QModelIndex lastAffectedIndex = m_pendingRemovals.last().index; QMutableListIterator it(m_pendingRemovals); while (it.hasNext()) { PendingRemoval removal = it.next(); m_pendingRemovalChildIndexes.remove(removal.sourceIndex); m_pendingRemovalParents.remove(parent.internalId()); it.remove(); Q_EMIT q->endRemoveRows(); } // qDebug() << "Remove done ##########"; // qDebug() << lastAffectedIndex << lastAffectedIndex.data() << lastAffectedRow; verifyStructure(lastAffectedIndex, lastAffectedRow - 1); } void KReparentingProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &, int) { // This could be several individual moves in the proxy model, or it could be no moves at all. // We can get the top indexes of the moved list and move those. // because their children won't be moved anywhere different. // I could look at the indexes between start and end (proxied could be several blocks), and move them to dest. // Then verify structure. // This could lead to an illegal move. // If we have // // Source: Proxy: // A A // B B // C - C // D - D // E E // // then source can legally move B to between C and D, however, implemented naively the proxymodel would attempt an illegal move. // We must first reparent everything below destRow in the proxy to the parent of parent in this case, then perform the move, then // verifyStructure. // // Moving B C and D to below E would be a legal move in the proxy model. // // Children of moved indexes which are not themselves moved must be first sent to their grandparents. // So if B and C were moved in the source model above to below E, D would first be moved to its grandparent, then B would be moved below E, // then the structure would need to be verified. // // Proxy start state: Intermediate state: Intermediate or final state: Possible alternative final state: // A A A A // B B E E // - C - C D - D // - D D B B // E E - C - C // So, I could iterate from start to end in proxySourceParent and if the depth goes less than parent, emit a block move, then start again. QHash newMappings = recreateMappings(parent, start, end); } void KReparentingProxyModelPrivate::sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) { } void KReparentingProxyModelPrivate::sourceLayoutAboutToBeChanged() { Q_Q(KReparentingProxyModel); q->beginResetModel(); return; #if 0 Q_EMIT q->layoutAboutToBeChanged(); Q_FOREACH (QPersistentModelIndex proxyPersistentIndex, q->persistentIndexList()) { m_proxyIndexes << proxyPersistentIndex; m_layoutChangePersistentIndexes << QPersistentModelIndex(q->mapToSource(proxyPersistentIndex)); } #endif } void KReparentingProxyModelPrivate::sourceLayoutChanged() { endResetProxy(); return; #if 0 Q_Q(KReparentingProxyModel); for (int i = 0; i < m_proxyIndexes.size(); ++i) { q->changePersistentIndex(m_proxyIndexes.at(i), q->mapFromSource(m_layoutChangePersistentIndexes.at(i))); } m_layoutChangePersistentIndexes.clear(); m_proxyIndexes.clear(); Q_EMIT q->layoutChanged(); #endif } void KReparentingProxyModelPrivate::sourceModelAboutToBeReset() { Q_Q(KReparentingProxyModel); q->beginResetModel(); } void KReparentingProxyModelPrivate::endResetProxy() { Q_Q(KReparentingProxyModel); m_parents.clear(); m_childIndexes.clear(); m_nextId = 0; m_pendingInsertions.clear(); m_pendingRemovals.clear(); m_pendingRemovalChildIndexes.clear(); m_pendingRemovalParents.clear(); // qDebug() << q->sourceModel()->rowCount(); QHash mappings = recreateMappings(QModelIndex(), 0, q->sourceModel()->rowCount() - 1, KReparentingProxyModelPrivate::MapDescendants); qDebug() << mappings; mergeDescendants(mappings, QModelIndex(), 0); q->endResetModel(); } void KReparentingProxyModelPrivate::sourceModelReset() { endResetProxy(); } void KReparentingProxyModelPrivate::emitDataChangedSignals(const QModelIndex &startIndex, int maxChanged) { Q_Q(KReparentingProxyModel); QModelIndex proxyParent = startIndex.parent(); int numChanged = 1; QModelIndex lastAffectedSibling = startIndex; QModelIndex proxySibling = getIndexBelow(startIndex, q); Q_FOREVER { if (proxySibling.parent() != proxyParent || numChanged >= maxChanged) { break; } numChanged++; lastAffectedSibling = proxySibling; proxySibling = getIndexBelow(proxySibling); } Q_EMIT q->dataChanged(startIndex, lastAffectedSibling); if (numChanged < maxChanged) { emitDataChangedSignals(proxySibling, maxChanged - numChanged); } } void KReparentingProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); Q_Q(KReparentingProxyModel); q->beginResetModel(); endResetProxy(); return; #if 0 QModelIndex parent = topLeft.parent(); const int start = topLeft.row(); const int end = bottomRight.row(); const int column = 0; const int maxChanged = end - start + 1; // Create mappings to the end because changing data can affect structure of siblings. verifyStructure(parent, start); // mapFromSource and emit signals. QModelIndex proxyStartIndex = q->mapFromSource(q->sourceModel()->index(start, column, parent)); emitDataChangedSignals(proxyStartIndex, maxChanged); #endif } Qt::DropActions KReparentingProxyModel::supportedDropActions() const { Q_ASSERT(sourceModel()); return sourceModel()->supportedDropActions(); } void KReparentingProxyModel::beginChangeRule() { Q_D(KReparentingProxyModel); d->sourceModelAboutToBeReset(); // beginResetModel(); // d->m_childIndexes.clear(); // d->m_layoutChangePersistentIndexes.clear(); // d->m_nextId = 1; // d->m_parents.clear(); // d->m_pendingInsertions.clear(); // d->m_pendingRemovalChildIndexes.clear(); // d->m_pendingRemovalParents.clear(); // d->m_pendingRemovals.clear(); // d->m_proxyIndexes.clear(); } void KReparentingProxyModel::endChangeRule() { Q_D(KReparentingProxyModel); d->endResetProxy(); return; } #include "moc_kreparentingproxymodel.cpp"