diff --git a/src/core/kdescendantsproxymodel.cpp b/src/core/kdescendantsproxymodel.cpp index 02681bb..91d513a 100644 --- a/src/core/kdescendantsproxymodel.cpp +++ b/src/core/kdescendantsproxymodel.cpp @@ -1,1101 +1,1122 @@ /* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company SPDX-FileContributor: Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kdescendantsproxymodel.h" #include #include "kbihash_p.h" typedef KHash2Map Mapping; class KDescendantsProxyModelPrivate { KDescendantsProxyModelPrivate(KDescendantsProxyModel *qq) : q_ptr(qq), m_rowCount(0), m_ignoreNextLayoutAboutToBeChanged(false), m_ignoreNextLayoutChanged(false), m_relayouting(false), m_displayAncestorData(false), m_ancestorSeparator(QStringLiteral(" / ")) { } Q_DECLARE_PUBLIC(KDescendantsProxyModel) KDescendantsProxyModel *const q_ptr; mutable QVector m_pendingParents; void scheduleProcessPendingParents() const; void processPendingParents(); void synchronousMappingRefresh(); void updateInternalIndexes(int start, int offset); void resetInternalData(); void sourceRowsAboutToBeInserted(const QModelIndex &, int, int); void sourceRowsInserted(const QModelIndex &, int, int); void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int); void sourceRowsRemoved(const QModelIndex &, int, int); void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int); void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); void sourceModelAboutToBeReset(); void sourceModelReset(); void sourceLayoutAboutToBeChanged(); void sourceLayoutChanged(); void sourceDataChanged(const QModelIndex &, const QModelIndex &); void sourceModelDestroyed(); Mapping m_mapping; int m_rowCount; QPair m_removePair; QPair m_insertPair; + bool m_expandsByDefault = true; bool m_ignoreNextLayoutAboutToBeChanged; bool m_ignoreNextLayoutChanged; bool m_relayouting; bool m_displayAncestorData; QString m_ancestorSeparator; QSet m_expandedSourceIndexes; QList m_layoutChangePersistentIndexes; QModelIndexList m_proxyIndexes; }; void KDescendantsProxyModelPrivate::resetInternalData() { m_rowCount = 0; m_mapping.clear(); m_layoutChangePersistentIndexes.clear(); m_proxyIndexes.clear(); } void KDescendantsProxyModelPrivate::synchronousMappingRefresh() { m_rowCount = 0; m_mapping.clear(); m_pendingParents.clear(); m_pendingParents.append(QModelIndex()); m_relayouting = true; while (!m_pendingParents.isEmpty()) { processPendingParents(); } m_relayouting = false; } void KDescendantsProxyModelPrivate::scheduleProcessPendingParents() const { const_cast(this)->processPendingParents(); } void KDescendantsProxyModelPrivate::processPendingParents() { Q_Q(KDescendantsProxyModel); const QVector::iterator begin = m_pendingParents.begin(); QVector::iterator it = begin; const QVector::iterator end = m_pendingParents.end(); QVector newPendingParents; while (it != end && it != m_pendingParents.end()) { const QModelIndex sourceParent = *it; if (!sourceParent.isValid() && m_rowCount > 0) { // It was removed from the source model before it was inserted. it = m_pendingParents.erase(it); continue; } if (!q->isSourceIndexExpanded(sourceParent)) { // It's a collapsed node, ignore. // it = m_pendingParents.erase(it); // continue; } const int rowCount = q->sourceModel()->rowCount(sourceParent); Q_ASSERT(rowCount > 0); const QPersistentModelIndex sourceIndex = q->sourceModel()->index(rowCount - 1, 0, sourceParent); Q_ASSERT(sourceIndex.isValid()); const QModelIndex proxyParent = q->mapFromSource(sourceParent); Q_ASSERT(sourceParent.isValid() == proxyParent.isValid()); const int proxyEndRow = proxyParent.row() + rowCount; const int proxyStartRow = proxyEndRow - rowCount + 1; if (!m_relayouting) { q->beginInsertRows(QModelIndex(), proxyStartRow, proxyEndRow); } updateInternalIndexes(proxyStartRow, rowCount); m_mapping.insert(sourceIndex, proxyEndRow); it = m_pendingParents.erase(it); m_rowCount += rowCount; if (!m_relayouting) { q->endInsertRows(); } for (int sourceRow = 0; sourceRow < rowCount; ++sourceRow) { static const int column = 0; const QModelIndex child = q->sourceModel()->index(sourceRow, column, sourceParent); Q_ASSERT(child.isValid()); if (q->sourceModel()->hasChildren(child)) { Q_ASSERT(q->sourceModel()->rowCount(child) > 0); if (q->isSourceIndexExpanded(child)) { newPendingParents.append(child); } } } } m_pendingParents += newPendingParents; if (!m_pendingParents.isEmpty()) { processPendingParents(); } // scheduleProcessPendingParents(); } void KDescendantsProxyModelPrivate::updateInternalIndexes(int start, int offset) { // TODO: Make KHash2Map support key updates and do this backwards. QHash updates; { Mapping::right_iterator it = m_mapping.rightLowerBound(start); const Mapping::right_iterator end = m_mapping.rightEnd(); while (it != end) { updates.insert(it.key() + offset, *it); ++it; } } { QHash::const_iterator it = updates.constBegin(); const QHash::const_iterator end = updates.constEnd(); for (; it != end; ++it) { m_mapping.insert(it.value(), it.key()); } } } KDescendantsProxyModel::KDescendantsProxyModel(QObject *parent) : QAbstractProxyModel(parent), d_ptr(new KDescendantsProxyModelPrivate(this)) { } KDescendantsProxyModel::~KDescendantsProxyModel() { delete d_ptr; } void KDescendantsProxyModel::expandChild(int row) { QModelIndex sourceIndex = mapToSource(index(row, 0)); if (!sourceModel()->hasChildren(sourceIndex) || isSourceIndexExpanded(sourceIndex)) { return; } d_ptr->m_expandedSourceIndexes << QPersistentModelIndex(sourceIndex); d_ptr->m_pendingParents << sourceIndex; d_ptr->scheduleProcessPendingParents(); emit sourceIndexExpanded(sourceIndex); } void KDescendantsProxyModel::collapseChild(int row) { QModelIndex sourceIndex = mapToSource(index(row, 0)); if (!sourceModel()->hasChildren(sourceIndex)) { return; } int rowStart = row + 1; int rowEnd = row; QList toVisit = {sourceIndex}; QSet visited; QModelIndex index; QModelIndex child; while (!toVisit.isEmpty()) { index = toVisit.takeLast(); if (!visited.contains(index)) { visited << index; rowEnd += sourceModel()->rowCount(index); for (int i = 0; i < sourceModel()->rowCount(index); ++i) { child = sourceModel()->index(i, 0, index); if (isSourceIndexExpanded(child)) { toVisit << child; } } } } d_ptr->m_expandedSourceIndexes.remove(QPersistentModelIndex(sourceIndex)); { Mapping::right_iterator it = d_ptr->m_mapping.rightLowerBound(rowStart); const Mapping::right_iterator endIt = d_ptr->m_mapping.rightUpperBound(rowEnd); if (endIt != d_ptr->m_mapping.rightEnd()) while (it != endIt) { it = d_ptr->m_mapping.eraseRight(it); } else while (it != d_ptr->m_mapping.rightUpperBound(rowEnd)) { it = d_ptr->m_mapping.eraseRight(it); } } d_ptr->m_removePair = qMakePair(rowStart, rowEnd); beginRemoveRows(QModelIndex(), rowStart, rowEnd); d_ptr->synchronousMappingRefresh(); endRemoveRows(); emit sourceIndexCollapsed(sourceIndex); } bool KDescendantsProxyModel::isRowExpanded(int row) const { return isSourceIndexExpanded(mapToSource(index(row, 0))); } int KDescendantsProxyModel::rowIndent(int row) const { int level = 0; QModelIndex sourceIndex = mapToSource(index(row, 0)); while (sourceIndex.isValid()) { ++level; sourceIndex = sourceIndex.parent(); } return level; } +void KDescendantsProxyModel::setExpandsByDefault(bool expand) +{ + d_ptr->m_expandsByDefault = expand; +} + +bool KDescendantsProxyModel::expandsByDefault() const +{ + return d_ptr->m_expandsByDefault; +} + bool KDescendantsProxyModel::isSourceIndexExpanded(const QModelIndex &sourceIndex) const { return !sourceIndex.isValid() || d_ptr->m_expandedSourceIndexes.contains(QPersistentModelIndex(sourceIndex)); } #if KITEMMODELS_BUILD_DEPRECATED_SINCE(4, 8) void KDescendantsProxyModel::setRootIndex(const QModelIndex &index) { Q_UNUSED(index) } #endif QModelIndexList KDescendantsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { return QAbstractProxyModel::match(start, role, value, hits, flags); } namespace { // we only work on DisplayRole for now static const QVector changedRoles = {Qt::DisplayRole}; } void KDescendantsProxyModel::setDisplayAncestorData(bool display) { Q_D(KDescendantsProxyModel); bool displayChanged = (display != d->m_displayAncestorData); d->m_displayAncestorData = display; if (displayChanged) { emit displayAncestorDataChanged(); // send out big hammer. Everything needs to be updated. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1), changedRoles); } } bool KDescendantsProxyModel::displayAncestorData() const { Q_D(const KDescendantsProxyModel); return d->m_displayAncestorData; } void KDescendantsProxyModel::setAncestorSeparator(const QString &separator) { Q_D(KDescendantsProxyModel); bool separatorChanged = (separator != d->m_ancestorSeparator); d->m_ancestorSeparator = separator; if (separatorChanged) { emit ancestorSeparatorChanged(); if (d->m_displayAncestorData) { // send out big hammer. Everything needs to be updated. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1), changedRoles); } } } QString KDescendantsProxyModel::ancestorSeparator() const { Q_D(const KDescendantsProxyModel); return d->m_ancestorSeparator; } void KDescendantsProxyModel::setSourceModel(QAbstractItemModel *_sourceModel) { Q_D(KDescendantsProxyModel); beginResetModel(); static const char *const modelSignals[] = { SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SIGNAL(modelAboutToBeReset()), SIGNAL(modelReset()), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SIGNAL(layoutAboutToBeChanged()), SIGNAL(layoutChanged()), SIGNAL(destroyed()) }; static const char *const proxySlots[] = { SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)), SLOT(sourceRowsInserted(QModelIndex,int,int)), SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(sourceRowsRemoved(QModelIndex,int,int)), SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(sourceModelAboutToBeReset()), SLOT(sourceModelReset()), SLOT(sourceDataChanged(QModelIndex,QModelIndex)), SLOT(sourceLayoutAboutToBeChanged()), SLOT(sourceLayoutChanged()), SLOT(sourceModelDestroyed()) }; if (sourceModel()) { for (int i = 0; i < int(sizeof modelSignals / sizeof * modelSignals); ++i) { disconnect(sourceModel(), modelSignals[i], this, proxySlots[i]); } } QAbstractProxyModel::setSourceModel(_sourceModel); d_ptr->m_expandedSourceIndexes.clear(); if (_sourceModel) { for (int i = 0; i < int(sizeof modelSignals / sizeof * modelSignals); ++i) { connect(_sourceModel, modelSignals[i], this, proxySlots[i]); } } resetInternalData(); if (_sourceModel && _sourceModel->hasChildren()) { d->synchronousMappingRefresh(); } endResetModel(); emit sourceModelChanged(); } QModelIndex KDescendantsProxyModel::parent(const QModelIndex &index) const { Q_UNUSED(index) return QModelIndex(); } bool KDescendantsProxyModel::hasChildren(const QModelIndex &parent) const { Q_D(const KDescendantsProxyModel); return !(d->m_mapping.isEmpty() || parent.isValid()); } int KDescendantsProxyModel::rowCount(const QModelIndex &parent) const { Q_D(const KDescendantsProxyModel); if (d->m_pendingParents.contains(parent) || parent.isValid() || !sourceModel()) { return 0; } if (d->m_mapping.isEmpty() && sourceModel()->hasChildren()) { Q_ASSERT(sourceModel()->rowCount() > 0); const_cast(d)->synchronousMappingRefresh(); } return d->m_rowCount; } QModelIndex KDescendantsProxyModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { return QModelIndex(); } if (!hasIndex(row, column, parent)) { return QModelIndex(); } return createIndex(row, column); } QModelIndex KDescendantsProxyModel::mapToSource(const QModelIndex &proxyIndex) const { Q_D(const KDescendantsProxyModel); if (d->m_mapping.isEmpty() || !proxyIndex.isValid() || !sourceModel()) { return QModelIndex(); } const Mapping::right_const_iterator result = d->m_mapping.rightLowerBound(proxyIndex.row()); Q_ASSERT(result != d->m_mapping.rightEnd()); const int proxyLastRow = result.key(); const QModelIndex sourceLastChild = result.value(); Q_ASSERT(sourceLastChild.isValid()); // proxyLastRow is greater than proxyIndex.row(). // sourceLastChild is vertically below the result we're looking for // and not necessarily in the correct parent. // We travel up through its parent hierarchy until we are in the // right parent, then return the correct sibling. // Source: Proxy: Row // - A - A - 0 // - B - B - 1 // - C - C - 2 // - D - D - 3 // - - E - E - 4 // - - F - F - 5 // - - G - G - 6 // - - H - H - 7 // - - I - I - 8 // - - - J - J - 9 // - - - K - K - 10 // - - - L - L - 11 // - - M - M - 12 // - - N - N - 13 // - O - O - 14 // Note that L, N and O are lastChildIndexes, and therefore have a mapping. If we // are trying to map G from the proxy to the source, We at this point have an iterator // pointing to (L -> 11). The proxy row of G is 6. (proxyIndex.row() == 6). We seek the // sourceIndex which is vertically above L by the distance proxyLastRow - proxyIndex.row(). // In this case the verticalDistance is 5. int verticalDistance = proxyLastRow - proxyIndex.row(); // We traverse the ancestors of L, until we can index the desired row in the source. QModelIndex ancestor = sourceLastChild; while (ancestor.isValid()) { const int ancestorRow = ancestor.row(); if (verticalDistance <= ancestorRow) { return ancestor.sibling(ancestorRow - verticalDistance, proxyIndex.column()); } verticalDistance -= (ancestorRow + 1); ancestor = ancestor.parent(); } Q_ASSERT(!"Didn't find target row."); return QModelIndex(); } QModelIndex KDescendantsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { Q_D(const KDescendantsProxyModel); if (!sourceModel()) { return QModelIndex(); } if (d->m_mapping.isEmpty()) { return QModelIndex(); } { // TODO: Consider a parent Mapping to speed this up. Mapping::right_const_iterator it = d->m_mapping.rightConstBegin(); const Mapping::right_const_iterator end = d->m_mapping.rightConstEnd(); const QModelIndex sourceParent = sourceIndex.parent(); Mapping::right_const_iterator result = end; if (!isSourceIndexExpanded(sourceParent)) { return QModelIndex(); } for (; it != end; ++it) { QModelIndex index = it.value(); bool found_block = false; while (index.isValid()) { const QModelIndex ancestor = index.parent(); if (ancestor == sourceParent && index.row() >= sourceIndex.row()) { found_block = true; if (result == end || it.key() < result.key()) { result = it; break; // Leave the while loop. index is still valid. } } index = ancestor; } if (found_block && !index.isValid()) // Looked through the ascendants of it.key() without finding sourceParent. // That means we've already got the result we need. { break; } } Q_ASSERT(result != end); const QModelIndex sourceLastChild = result.value(); int proxyRow = result.key(); QModelIndex index = sourceLastChild; while (index.isValid()) { const QModelIndex ancestor = index.parent(); if (ancestor == sourceParent) { return createIndex(proxyRow - (index.row() - sourceIndex.row()), sourceIndex.column()); } proxyRow -= (index.row() + 1); index = ancestor; } Q_ASSERT(!"Didn't find valid proxy mapping."); return QModelIndex(); } } int KDescendantsProxyModel::columnCount(const QModelIndex &parent) const { if (parent.isValid() /* || rowCount(parent) == 0 */ || !sourceModel()) { return 0; } return sourceModel()->columnCount(); } QVariant KDescendantsProxyModel::data(const QModelIndex &index, int role) const { Q_D(const KDescendantsProxyModel); if (!sourceModel()) { return QVariant(); } if (!index.isValid()) { return sourceModel()->data(index, role); } QModelIndex sourceIndex = mapToSource(index); if ((d->m_displayAncestorData) && (role == Qt::DisplayRole)) { if (!sourceIndex.isValid()) { return QVariant(); } QString displayData = sourceIndex.data().toString(); sourceIndex = sourceIndex.parent(); while (sourceIndex.isValid()) { displayData.prepend(d->m_ancestorSeparator); displayData.prepend(sourceIndex.data().toString()); sourceIndex = sourceIndex.parent(); } return displayData; } else { return sourceIndex.data(role); } } QVariant KDescendantsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (!sourceModel() || columnCount() <= section) { return QVariant(); } return QAbstractProxyModel::headerData(section, orientation, role); } Qt::ItemFlags KDescendantsProxyModel::flags(const QModelIndex &index) const { if (!index.isValid() || !sourceModel()) { return QAbstractProxyModel::flags(index); } const QModelIndex srcIndex = mapToSource(index); Q_ASSERT(srcIndex.isValid()); return sourceModel()->flags(srcIndex); } void KDescendantsProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_Q(KDescendantsProxyModel); if (parent.isValid() && !q->isSourceIndexExpanded(parent)) { return; } if (!q->sourceModel()->hasChildren(parent)) { Q_ASSERT(q->sourceModel()->rowCount(parent) == 0); // parent was not a parent before. return; } int proxyStart = -1; const int rowCount = q->sourceModel()->rowCount(parent); if (rowCount > start) { const QModelIndex belowStart = q->sourceModel()->index(start, 0, parent); proxyStart = q->mapFromSource(belowStart).row(); } else if (rowCount == 0) { proxyStart = q->mapFromSource(parent).row() + 1; } else { Q_ASSERT(rowCount == start); static const int column = 0; QModelIndex idx = q->sourceModel()->index(rowCount - 1, column, parent); while (q->isSourceIndexExpanded(idx) && q->sourceModel()->hasChildren(idx)) { Q_ASSERT(q->sourceModel()->rowCount(idx) > 0); idx = q->sourceModel()->index(q->sourceModel()->rowCount(idx) - 1, column, idx); } // The last item in the list is getting a sibling below it. proxyStart = q->mapFromSource(idx).row() + 1; } const int proxyEnd = proxyStart + (end - start); m_insertPair = qMakePair(proxyStart, proxyEnd); q->beginInsertRows(QModelIndex(), proxyStart, proxyEnd); } void KDescendantsProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int start, int end) { Q_Q(KDescendantsProxyModel); if (parent.isValid() && !q->isSourceIndexExpanded(parent)) { return; } Q_ASSERT(q->sourceModel()->index(start, 0, parent).isValid()); const int rowCount = q->sourceModel()->rowCount(parent); Q_ASSERT(rowCount > 0); const int difference = end - start + 1; if (rowCount == difference) { // @p parent was not a parent before. m_pendingParents.append(parent); scheduleProcessPendingParents(); return; } const int proxyStart = m_insertPair.first; Q_ASSERT(proxyStart >= 0); updateInternalIndexes(proxyStart, difference); if (rowCount - 1 == end) { // The previously last row (the mapped one) is no longer the last. // For example, // - A - A 0 // - - B - B 1 // - - C - C 2 // - - - D - D 3 // - - - E -> - E 4 // - - F - F 5 // - - G -> - G 6 // - H - H 7 // - I -> - I 8 // As last children, E, F and G have mappings. // Consider that 'J' is appended to the children of 'C', below 'E'. // - A - A 0 // - - B - B 1 // - - C - C 2 // - - - D - D 3 // - - - E -> - E 4 // - - - J - ??? 5 // - - F - F 6 // - - G -> - G 7 // - H - H 8 // - I -> - I 9 // The updateInternalIndexes call above will have updated the F and G mappings correctly because proxyStart is 5. // That means that E -> 4 was not affected by the updateInternalIndexes call. // Now the mapping for E -> 4 needs to be updated so that it's a mapping for J -> 5. Q_ASSERT(!m_mapping.isEmpty()); static const int column = 0; const QModelIndex oldIndex = q->sourceModel()->index(rowCount - 1 - difference, column, parent); Q_ASSERT(m_mapping.leftContains(oldIndex)); const QModelIndex newIndex = q->sourceModel()->index(rowCount - 1, column, parent); QModelIndex indexAbove = oldIndex; if (start > 0) { // If we have something like this: // // - A // - - B // - - C // // and we then insert D as a sibling of A below it, we need to remove the mapping for A, // and the row number used for D must take into account the descendants of A. while (q->isSourceIndexExpanded(indexAbove) && q->sourceModel()->hasChildren(indexAbove)) { Q_ASSERT(q->sourceModel()->rowCount(indexAbove) > 0); indexAbove = q->sourceModel()->index(q->sourceModel()->rowCount(indexAbove) - 1, column, indexAbove); } Q_ASSERT(!q->isSourceIndexExpanded(indexAbove) || q->sourceModel()->rowCount(indexAbove) == 0); } Q_ASSERT(m_mapping.leftContains(indexAbove)); const int newProxyRow = m_mapping.leftToRight(indexAbove) + difference; // oldIndex is E in the source. proxyRow is 4. m_mapping.removeLeft(oldIndex); // newIndex is J. (proxyRow + difference) is 5. m_mapping.insert(newIndex, newProxyRow); } for (int row = start; row <= end; ++row) { static const int column = 0; const QModelIndex idx = q->sourceModel()->index(row, column, parent); + m_expandedSourceIndexes << QPersistentModelIndex(idx); Q_ASSERT(idx.isValid()); if (q->isSourceIndexExpanded(idx) && q->sourceModel()->hasChildren(idx)) { Q_ASSERT(q->sourceModel()->rowCount(idx) > 0); m_pendingParents.append(idx); } } m_rowCount += difference; q->endInsertRows(); scheduleProcessPendingParents(); } void KDescendantsProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_Q(KDescendantsProxyModel); const int proxyStart = q->mapFromSource(q->sourceModel()->index(start, 0, parent)).row(); static const int column = 0; QModelIndex idx = q->sourceModel()->index(end, column, parent); while (q->sourceModel()->hasChildren(idx)) { Q_ASSERT(q->sourceModel()->rowCount(idx) > 0); idx = q->sourceModel()->index(q->sourceModel()->rowCount(idx) - 1, column, idx); } const int proxyEnd = q->mapFromSource(idx).row(); + for(int i = start; i <= end; ++i) { + QModelIndex idx = q->sourceModel()->index(i, column, parent); + m_expandedSourceIndexes.remove(QPersistentModelIndex(idx)); + } + m_removePair = qMakePair(proxyStart, proxyEnd); q->beginRemoveRows(QModelIndex(), proxyStart, proxyEnd); } static QModelIndex getFirstDeepest(QAbstractItemModel *model, const QModelIndex &parent, int *count) { static const int column = 0; Q_ASSERT(model->hasChildren(parent)); Q_ASSERT(model->rowCount(parent) > 0); for (int row = 0; row < model->rowCount(parent); ++row) { (*count)++; const QModelIndex child = model->index(row, column, parent); Q_ASSERT(child.isValid()); if (model->hasChildren(child)) { return getFirstDeepest(model, child, count); } } return model->index(model->rowCount(parent) - 1, column, parent); } void KDescendantsProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int start, int end) { Q_Q(KDescendantsProxyModel); Q_UNUSED(end) const int rowCount = q->sourceModel()->rowCount(parent); const int proxyStart = m_removePair.first; const int proxyEnd = m_removePair.second; const int difference = proxyEnd - proxyStart + 1; { Mapping::right_iterator it = m_mapping.rightLowerBound(proxyStart); const Mapping::right_iterator endIt = m_mapping.rightUpperBound(proxyEnd); if (endIt != m_mapping.rightEnd()) while (it != endIt) { it = m_mapping.eraseRight(it); } else while (it != m_mapping.rightUpperBound(proxyEnd)) { it = m_mapping.eraseRight(it); } } m_removePair = qMakePair(-1, -1); m_rowCount -= difference; Q_ASSERT(m_rowCount >= 0); updateInternalIndexes(proxyStart, -1 * difference); if (rowCount != start || rowCount == 0) { q->endRemoveRows(); return; } static const int column = 0; const QModelIndex newEnd = q->sourceModel()->index(rowCount - 1, column, parent); Q_ASSERT(newEnd.isValid()); if (m_mapping.isEmpty()) { m_mapping.insert(newEnd, newEnd.row()); q->endRemoveRows(); return; } if (q->sourceModel()->hasChildren(newEnd)) { int count = 0; const QModelIndex firstDeepest = getFirstDeepest(q->sourceModel(), newEnd, &count); Q_ASSERT(firstDeepest.isValid()); const int firstDeepestProxy = m_mapping.leftToRight(firstDeepest); m_mapping.insert(newEnd, firstDeepestProxy - count); q->endRemoveRows(); return; } Mapping::right_iterator lowerBound = m_mapping.rightLowerBound(proxyStart); if (lowerBound == m_mapping.rightEnd()) { int proxyRow = (lowerBound - 1).key(); for (int row = newEnd.row(); row >= 0; --row) { const QModelIndex newEndSibling = q->sourceModel()->index(row, column, parent); if (!q->sourceModel()->hasChildren(newEndSibling)) { ++proxyRow; } else { break; } } m_mapping.insert(newEnd, proxyRow); q->endRemoveRows(); return; } else if (lowerBound == m_mapping.rightBegin()) { int proxyRow = rowCount - 1; QModelIndex trackedParent = parent; while (trackedParent.isValid()) { proxyRow += (trackedParent.row() + 1); trackedParent = trackedParent.parent(); } m_mapping.insert(newEnd, proxyRow); q->endRemoveRows(); return; } const Mapping::right_iterator boundAbove = lowerBound - 1; QVector targetParents; targetParents.push_back(parent); { QModelIndex target = parent; int count = 0; while (target.isValid()) { if (target == boundAbove.value()) { m_mapping.insert(newEnd, count + boundAbove.key() + newEnd.row() + 1); q->endRemoveRows(); return; } count += (target.row() + 1); target = target.parent(); if (target.isValid()) { targetParents.push_back(target); } } } QModelIndex boundParent = boundAbove.value().parent(); QModelIndex prevParent = boundParent; Q_ASSERT(boundParent.isValid()); while (boundParent.isValid()) { prevParent = boundParent; boundParent = boundParent.parent(); if (targetParents.contains(prevParent)) { break; } if (!m_mapping.leftContains(prevParent)) { break; } if (m_mapping.leftToRight(prevParent) > boundAbove.key()) { break; } } QModelIndex trackedParent = parent; int proxyRow = boundAbove.key(); Q_ASSERT(prevParent.isValid()); proxyRow -= prevParent.row(); while (trackedParent != boundParent) { proxyRow += (trackedParent.row() + 1); trackedParent = trackedParent.parent(); } m_mapping.insert(newEnd, proxyRow + newEnd.row()); q->endRemoveRows(); } void KDescendantsProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destStart) { Q_UNUSED(srcParent) Q_UNUSED(srcStart) Q_UNUSED(srcEnd) Q_UNUSED(destParent) Q_UNUSED(destStart) sourceLayoutAboutToBeChanged(); } void KDescendantsProxyModelPrivate::sourceRowsMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destStart) { Q_UNUSED(srcParent) Q_UNUSED(srcStart) Q_UNUSED(srcEnd) Q_UNUSED(destParent) Q_UNUSED(destStart) sourceLayoutChanged(); } void KDescendantsProxyModelPrivate::sourceModelAboutToBeReset() { Q_Q(KDescendantsProxyModel); q->beginResetModel(); } void KDescendantsProxyModelPrivate::sourceModelReset() { Q_Q(KDescendantsProxyModel); resetInternalData(); if (q->sourceModel()->hasChildren()) { Q_ASSERT(q->sourceModel()->rowCount() > 0); m_pendingParents.append(QModelIndex()); scheduleProcessPendingParents(); } q->endResetModel(); } void KDescendantsProxyModelPrivate::sourceLayoutAboutToBeChanged() { Q_Q(KDescendantsProxyModel); if (m_ignoreNextLayoutChanged) { m_ignoreNextLayoutChanged = false; return; } if (m_mapping.isEmpty()) { return; } emit q->layoutAboutToBeChanged(); QPersistentModelIndex srcPersistentIndex; const auto lst = q->persistentIndexList(); for (const QPersistentModelIndex &proxyPersistentIndex : lst) { m_proxyIndexes << proxyPersistentIndex; Q_ASSERT(proxyPersistentIndex.isValid()); srcPersistentIndex = q->mapToSource(proxyPersistentIndex); Q_ASSERT(srcPersistentIndex.isValid()); m_layoutChangePersistentIndexes << srcPersistentIndex; } } void KDescendantsProxyModelPrivate::sourceLayoutChanged() { Q_Q(KDescendantsProxyModel); if (m_ignoreNextLayoutAboutToBeChanged) { m_ignoreNextLayoutAboutToBeChanged = false; return; } if (m_mapping.isEmpty()) { return; } m_rowCount = 0; synchronousMappingRefresh(); 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(); emit q->layoutChanged(); } void KDescendantsProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { Q_Q(KDescendantsProxyModel); Q_ASSERT(topLeft.model() == q->sourceModel()); Q_ASSERT(bottomRight.model() == q->sourceModel()); + if (!q->isSourceIndexExpanded(topLeft.parent())) { + return; + } + const int topRow = topLeft.row(); const int bottomRow = bottomRight.row(); for (int i = topRow; i <= bottomRow; ++i) { const QModelIndex sourceTopLeft = q->sourceModel()->index(i, topLeft.column(), topLeft.parent()); Q_ASSERT(sourceTopLeft.isValid()); const QModelIndex proxyTopLeft = q->mapFromSource(sourceTopLeft); // TODO. If an index does not have any descendants, then we can emit in blocks of rows. // As it is we emit once for each row. const QModelIndex sourceBottomRight = q->sourceModel()->index(i, bottomRight.column(), bottomRight.parent()); const QModelIndex proxyBottomRight = q->mapFromSource(sourceBottomRight); Q_ASSERT(proxyTopLeft.isValid()); Q_ASSERT(proxyBottomRight.isValid()); emit q->dataChanged(proxyTopLeft, proxyBottomRight); } } void KDescendantsProxyModelPrivate::sourceModelDestroyed() { resetInternalData(); } QMimeData *KDescendantsProxyModel::mimeData(const QModelIndexList &indexes) const { if (!sourceModel()) { return QAbstractProxyModel::mimeData(indexes); } Q_ASSERT(sourceModel()); QModelIndexList sourceIndexes; for (const QModelIndex &index : indexes) { sourceIndexes << mapToSource(index); } return sourceModel()->mimeData(sourceIndexes); } QStringList KDescendantsProxyModel::mimeTypes() const { if (!sourceModel()) { return QAbstractProxyModel::mimeTypes(); } Q_ASSERT(sourceModel()); return sourceModel()->mimeTypes(); } Qt::DropActions KDescendantsProxyModel::supportedDropActions() const { if (!sourceModel()) { return QAbstractProxyModel::supportedDropActions(); } return sourceModel()->supportedDropActions(); } #include "moc_kdescendantsproxymodel.cpp" diff --git a/src/core/kdescendantsproxymodel.h b/src/core/kdescendantsproxymodel.h index 327d6bc..e716963 100644 --- a/src/core/kdescendantsproxymodel.h +++ b/src/core/kdescendantsproxymodel.h @@ -1,225 +1,227 @@ /* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KDESCENDANTSPROXYMODEL_P_H #define KDESCENDANTSPROXYMODEL_P_H #include class KDescendantsProxyModelPrivate; #include "kitemmodels_export.h" /** @class KDescendantsProxyModel kdescendantsproxymodel.h KDescendantsProxyModel @brief Proxy Model for restructuring a Tree into a list. A KDescendantsProxyModel may be used to alter how the items in the tree are presented. Given a model which is represented as a tree: \image html entitytreemodel.png "A plain EntityTreeModel in a view" The KDescendantsProxyModel restructures the sourceModel to represent it as a flat list. @code // ... Create an entityTreeModel KDescendantsProxyModel *descProxy = new KDescendantsProxyModel(this); descProxy->setSourceModel(entityTree); view->setModel(descProxy); @endcode \image html descendantentitiesproxymodel.png "A KDescendantsProxyModel." KDescendantEntitiesProxyModel can also display the ancestors of the index in the source model as part of its display. @code // ... Create an entityTreeModel KDescendantsProxyModel *descProxy = new KDescendantsProxyModel(this); descProxy->setSourceModel(entityTree); // #### This is new descProxy->setDisplayAncestorData(true); descProxy->setAncestorSeparator(QString(" / ")); view->setModel(descProxy); @endcode \image html descendantentitiesproxymodel-withansecnames.png "A KDescendantsProxyModel with ancestor names." @since 4.6 @author Stephen Kelly */ class KITEMMODELS_EXPORT KDescendantsProxyModel : public QAbstractProxyModel { Q_OBJECT /** * @since 5.62 */ Q_PROPERTY(QAbstractItemModel *model READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) /** * @since 5.62 */ Q_PROPERTY(bool displayAncestorData READ displayAncestorData WRITE setDisplayAncestorData NOTIFY displayAncestorDataChanged) /** * @since 5.62 */ Q_PROPERTY(QString ancestorSeparator READ ancestorSeparator WRITE setAncestorSeparator NOTIFY ancestorSeparatorChanged) public: /** * Creates a new descendant entities proxy model. * * @param parent The parent object. */ explicit KDescendantsProxyModel(QObject *parent = nullptr); /** * Destroys the descendant entities proxy model. */ ~KDescendantsProxyModel() override; /** * Sets the source @p model of the proxy. */ void setSourceModel(QAbstractItemModel *model) override; #if KITEMMODELS_ENABLE_DEPRECATED_SINCE(4, 8) /** * @deprecated Since 4.8 * * This method does nothing. */ KITEMMODELS_DEPRECATED_VERSION(4, 8, "Method is a no-op.") void setRootIndex(const QModelIndex &index); #endif /** * Set whether to show ancestor data in the model. If @p display is true, then * a source model which is displayed as * * @code * -> "Item 0-0" (this is row-depth) * -> -> "Item 0-1" * -> -> "Item 1-1" * -> -> -> "Item 0-2" * -> -> -> "Item 1-2" * -> "Item 1-0" * @endcode * * will be displayed as * * @code * -> *Item 0-0" * -> "Item 0-0 / Item 0-1" * -> "Item 0-0 / Item 1-1" * -> "Item 0-0 / Item 1-1 / Item 0-2" * -> "Item 0-0 / Item 1-1 / Item 1-2" * -> "Item 1-0" * @endcode * * If @p display is false, the proxy will show * * @code * -> *Item 0-0" * -> "Item 0-1" * -> "Item 1-1" * -> "Item 0-2" * -> "Item 1-2" * -> "Item 1-0" * @endcode * * Default is false. */ void setDisplayAncestorData(bool display); /** * Whether ancestor data will be displayed. */ bool displayAncestorData() const; /** * Sets the ancestor @p separator used between data of ancestors. */ void setAncestorSeparator(const QString &separator); /** * Separator used between data of ancestors. */ QString ancestorSeparator() const; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; QStringList mimeTypes() const override; bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &) const override; int columnCount(const QModelIndex &index = QModelIndex()) const override; + void setExpandsByDefault(bool expand); + bool expandsByDefault() const; bool isSourceIndexExpanded(const QModelIndex &sourceIndex) const; Q_INVOKABLE void expandChild(int row); //TODO: in QML version Q_INVOKABLE void collapseChild(int row); //TODO: in QML version Q_INVOKABLE bool isRowExpanded(int row) const; //TODO: extra role Q_INVOKABLE int rowIndent(int row) const; //TODO: extra role Qt::DropActions supportedDropActions() const override; /** Reimplemented to match all descendants. */ virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; Q_SIGNALS: void sourceModelChanged(); void displayAncestorDataChanged(); void ancestorSeparatorChanged(); void sourceIndexExpanded(const QModelIndex &sourceIndex); void sourceIndexCollapsed(const QModelIndex &sourceIndex); private: Q_DECLARE_PRIVATE(KDescendantsProxyModel) //@cond PRIVATE KDescendantsProxyModelPrivate *d_ptr; Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int)) Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int)) Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int)) Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int)) Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)) Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)) Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset()) Q_PRIVATE_SLOT(d_func(), void sourceModelReset()) Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged()) Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged()) Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &)) Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed()) Q_PRIVATE_SLOT(d_func(), void processPendingParents()) // Make these private, they shouldn't be called by applications // virtual bool insertRows(int , int, const QModelIndex & = QModelIndex()); // virtual bool insertColumns(int, int, const QModelIndex & = QModelIndex()); // virtual bool removeRows(int, int, const QModelIndex & = QModelIndex()); // virtual bool removeColumns(int, int, const QModelIndex & = QModelIndex()); //@endcond }; #endif