diff --git a/CMakeLists.txt b/CMakeLists.txt index 74cec94..98236e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,85 +1,86 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.71.0") # handled by release scripts project(KItemModels VERSION ${KF5_VERSION}) include(FeatureSummary) find_package(ECM 5.70.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMQtDeclareLoggingCategory) set(REQUIRED_QT_VERSION 5.12.0) find_package(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) find_package(Qt5Qml ${REQUIRED_QT_VERSION} NO_MODULE) include(ECMGenerateExportHeader) set_package_properties(Qt5Qml PROPERTIES TYPE OPTIONAL PURPOSE "Build QML import for KItemModels" ) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KITEMMODELS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kitemmodels_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ItemModelsConfigVersion.cmake" SOVERSION 5) +add_definitions(-DQT_NO_KEYWORDS) add_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5ItemModels") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5ItemModels_QCH FILE KF5ItemModelsQCHTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ItemModelsQCHTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5ItemModelsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5ItemModelsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ItemModelsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5ItemModelsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5ItemModelsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ItemModelsTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kitemmodels_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/proxymodeltestsuite/dynamictreemodel.cpp b/autotests/proxymodeltestsuite/dynamictreemodel.cpp index 3d45b5d..61f673a 100644 --- a/autotests/proxymodeltestsuite/dynamictreemodel.cpp +++ b/autotests/proxymodeltestsuite/dynamictreemodel.cpp @@ -1,937 +1,937 @@ /* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "dynamictreemodel.h" #include #include #include #include #include // If DUMPTREE is defined, ModelInsertCommand dumps the tree of what it is inserting. // #define DUMPTREE #ifdef DUMPTREE #include #endif DynamicTreeModel::DynamicTreeModel(QObject *parent) : QAbstractItemModel(parent), nextId(1) { } QModelIndex DynamicTreeModel::index(int row, int column, const QModelIndex &parent) const { // if (column != 0) // return QModelIndex(); if (column < 0 || row < 0) { return QModelIndex(); } QList > childIdColumns = m_childItems.value(parent.internalId()); const qint64 grandParent = findParentId(parent.internalId()); if (grandParent >= 0) { QList > parentTable = m_childItems.value(grandParent); Q_ASSERT(parent.column() < parentTable.size()); QList parentSiblings = parentTable.at(parent.column()); Q_ASSERT(parent.row() < parentSiblings.size()); } if (childIdColumns.size() == 0) { return QModelIndex(); } if (column >= childIdColumns.size()) { return QModelIndex(); } QList rowIds = childIdColumns.at(column); if (row >= rowIds.size()) { return QModelIndex(); } qint64 id = rowIds.at(row); return createIndex(row, column, reinterpret_cast(id)); } qint64 DynamicTreeModel::findParentId(qint64 searchId) const { if (searchId <= 0) { return -1; } QHashIterator > > i(m_childItems); while (i.hasNext()) { i.next(); QListIterator > j(i.value()); while (j.hasNext()) { QList l = j.next(); if (l.contains(searchId)) { return i.key(); } } } return -1; } QModelIndex DynamicTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } qint64 searchId = index.internalId(); qint64 parentId = findParentId(searchId); // Will never happen for valid index, but what the hey... if (parentId <= 0) { return QModelIndex(); } qint64 grandParentId = findParentId(parentId); if (grandParentId < 0) { grandParentId = 0; } int column = 0; QList childList = m_childItems.value(grandParentId).at(column); int row = childList.indexOf(parentId); return createIndex(row, column, reinterpret_cast(parentId)); } int DynamicTreeModel::rowCount(const QModelIndex &index) const { QList > cols = m_childItems.value(index.internalId()); if (cols.size() == 0) { return 0; } if (index.column() > 0) { return 0; } return cols.at(0).size(); } int DynamicTreeModel::columnCount(const QModelIndex &index) const { // Q_UNUSED(index); return m_childItems.value(index.internalId()).size(); } QVariant DynamicTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (DynamicTreeModelId == role) { return index.internalId(); } if (Qt::DisplayRole == role || Qt::EditRole == role) { return m_items.value(index.internalId()); } return QVariant(); } bool DynamicTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { m_items[index.internalId()] = value.toString(); dataChanged(index, index); return true; } return QAbstractItemModel::setData(index, value, role); } void DynamicTreeModel::clear() { beginResetModel(); m_items.clear(); m_childItems.clear(); nextId = 1; endResetModel(); } bool DynamicTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int _column, const QModelIndex &parent) { Q_UNUSED(action); Q_UNUSED(_column); QByteArray encoded = data->data(mimeTypes().at(0)); QHash > movedItems; bool ok; qint64 id; int _row; static const int column = 0; QHash > >::const_iterator it; const auto lst = encoded.split('\0'); for (const QByteArray &ba : lst) { id = ba.toInt(&ok); if (!ok) { qDebug() << ba; } Q_ASSERT(ok); _row = -1; for (it = m_childItems.constBegin(); it != m_childItems.constEnd(); ++it) { _row = it.value().first().indexOf(id); if (_row < 0) { continue; } movedItems[createIndex(_row, column, reinterpret_cast(id)).parent()].append(_row); break; } Q_ASSERT(_row >= 0); if (_row < 0) { return false; } } const int destRow = row < 0 ? 0 : row; const QList destPath = indexToPath(parent); QList srcPath; QModelIndex srcParent; QHash >::iterator src_parent_it = movedItems.begin(); int startRow = 0; int endRow = 0; int nextMovedRow = 0; QList rowsMoved; QList::iterator src_row_it; QList::iterator rows_moved_end; QList moveCommands; for (; src_parent_it != movedItems.end(); ++src_parent_it) { srcParent = src_parent_it.key(); srcPath = indexToPath(srcParent); rowsMoved = src_parent_it.value(); std::sort(rowsMoved.begin(), rowsMoved.end()); src_row_it = rowsMoved.begin(); rows_moved_end = rowsMoved.end(); startRow = *src_row_it; endRow = startRow; ++src_row_it; if (src_row_it == rows_moved_end) { moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); continue; } for (; src_row_it != rows_moved_end; ++src_row_it) { nextMovedRow = *src_row_it; if (nextMovedRow == endRow + 1) { ++endRow; } else { Q_ASSERT(nextMovedRow > endRow + 1); moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); startRow = nextMovedRow; endRow = nextMovedRow; if ((src_row_it + 1) == rows_moved_end) { moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); } } } } QPersistentModelIndex destParent = parent; QPersistentModelIndex destRowIndex = index(destRow, column, parent); ModelMoveCommand *firstCommand = moveCommands.takeFirst(); firstCommand->setDestAncestors(indexToPath(parent)); firstCommand->setDestRow(destRow); firstCommand->doCommand(); if (!destRowIndex.isValid()) { destRowIndex = index(destRow, column, parent); } int offset = firstCommand->endRow() - firstCommand->startRow() + 1; for (ModelMoveCommand *moveCommand : qAsConst(moveCommands)) { moveCommand->setDestAncestors(indexToPath(destParent)); moveCommand->setDestRow(destRowIndex.row() + offset); moveCommand->doCommand(); offset = moveCommand->endRow() - moveCommand->startRow() + 1; } return false; } ModelMoveCommand *DynamicTreeModel::getMoveCommand(const QList &srcPath, int startRow, int endRow) { ModelMoveCommand *moveCommand = new ModelMoveCommand(this, this); moveCommand->setAncestorRowNumbers(srcPath); moveCommand->setStartRow(startRow); moveCommand->setEndRow(endRow); return moveCommand; } Qt::ItemFlags DynamicTreeModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.isValid()) { return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; } return flags; } Qt::DropActions DynamicTreeModel::supportedDropActions() const { return Qt::MoveAction; } QStringList DynamicTreeModel::mimeTypes() const { QStringList types; types << QStringLiteral("application/x-dynamictreemodel-itemlist"); return types; } QMimeData *DynamicTreeModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QByteArray itemData; QModelIndexList::const_iterator it = indexes.begin(); const QModelIndexList::const_iterator end = indexes.end(); while (it != end) { itemData.append(QByteArray::number(it->internalId())); ++it; if (it != end) { itemData.append('\0'); } } data->setData(mimeTypes().at(0), itemData); return data; } QList DynamicTreeModel::indexToPath(const QModelIndex &_idx) const { QList list; QModelIndex idx = _idx; while (idx.isValid()) { list.prepend(idx.row()); idx = idx.parent(); } return list; } QModelIndexList DynamicTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role != DynamicTreeModelId && role != Qt::DisplayRole) { return QAbstractItemModel::match(start, role, value, hits, flags); } qint64 id = value.toLongLong(); if (role == Qt::DisplayRole) { id = m_items.key(value.toString()); } QHash > >::const_iterator it = m_childItems.constBegin(); const QHash > >::const_iterator end = m_childItems.constEnd(); QList > items; QList >::const_iterator itemIt; QList >::const_iterator itemEnd; int foundIndexRow; for (; it != end; ++it) { items = it.value(); itemEnd = items.constEnd(); for (itemIt = items.constBegin(); itemIt != itemEnd; ++itemIt) { foundIndexRow = itemIt->indexOf(id); if (foundIndexRow != -1) { static const int column = 0; return QModelIndexList() << createIndex(foundIndexRow, column, reinterpret_cast(id)); } } } return QModelIndexList(); } ModelChangeCommand::ModelChangeCommand(DynamicTreeModel *model, QObject *parent) : QObject(parent), m_model(model), m_startRow(-1), m_endRow(-1), m_numCols(1) { } QModelIndex ModelChangeCommand::findIndex(const QList &rows) const { const int col = 0; QModelIndex parent = QModelIndex(); QListIterator i(rows); while (i.hasNext()) { parent = m_model->index(i.next(), col, parent); Q_ASSERT(parent.isValid()); } return parent; } ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } QList ModelInsertCommand::tokenize(const QString &treeString) const { QStringList parts = treeString.split(QStringLiteral("-")); QList tokens; const QStringList::const_iterator begin = parts.constBegin(); const QStringList::const_iterator end = parts.constEnd(); QStringList::const_iterator it = begin; ++it; for (; it != end; ++it) { Token token; if (it->trimmed().isEmpty()) { token.type = Token::Branch; } else { token.type = Token::Leaf; token.content = *it; } tokens.append(token); } return tokens; } void ModelInsertCommand::interpret(const QString &treeString) { m_treeString = treeString; const QList depths = getDepths(m_treeString); const int size = std::count(depths.begin(), depths.end(), 0); Q_ASSERT(size != 0); m_endRow = m_startRow + size - 1; } QList ModelInsertCommand::getDepths(const QString &treeString) const { int depth = 0; QList depths; #ifdef DUMPTREE int id = 1; #endif QList tokens = tokenize(treeString); while (!tokens.isEmpty()) { Token token = tokens.takeFirst(); if (token.type == Token::Branch) { ++depth; continue; } Q_ASSERT(token.type == Token::Leaf); depths.append(depth); #ifdef DUMPTREE std::cout << "\""; for (int i = 0; i <= depth; ++i) { std::cout << " -"; } std::cout << " " << id++ << "\"" << std::endl; #endif depth = 0; } return depths; } void ModelInsertCommand::doCommand() { QModelIndex parent = findIndex(m_rowNumbers); if (!m_treeString.isEmpty()) { const QList depths = getDepths(m_treeString); const int size = std::count(depths.begin(), depths.end(), 0); Q_ASSERT(size != 0); m_endRow = m_startRow + size - 1; } m_model->beginInsertRows(parent, m_startRow, m_endRow); if (!m_treeString.isEmpty()) { doInsertTree(parent); } else { qint64 parentId = parent.internalId(); for (int row = m_startRow; row <= m_endRow; row++) { for (int col = 0; col < m_numCols; col++) { if (m_model->m_childItems[parentId].size() <= col) { m_model->m_childItems[parentId].append(QList()); } qint64 id = m_model->newId(); QString name = QString::number(id); m_model->m_items.insert(id, name); m_model->m_childItems[parentId][col].insert(row, id); } } } m_model->endInsertRows(); } void ModelInsertCommand::doInsertTree(const QModelIndex &fragmentParent) { const QList depths = getDepths(m_treeString); qint64 fragmentParentIdentifier = fragmentParent.internalId(); QList::const_iterator it = depths.constBegin(); const QList::const_iterator end = depths.constEnd(); QList recentParents; recentParents.append(fragmentParentIdentifier); qint64 lastId = 0; qint64 id; QString name; int depth = 0; int row = m_startRow; Q_ASSERT(*it == depth); QList rows; rows.append(row); for (; it != end; ++it) { if (*it > depth) { Q_ASSERT(*it == depth + 1); fragmentParentIdentifier = lastId; if (recentParents.size() == *it) { recentParents.append(fragmentParentIdentifier); } else { recentParents[*it] = fragmentParentIdentifier; } ++depth; } else if (*it < depth) { fragmentParentIdentifier = recentParents.at(*it); depth = (*it); } if (rows.size() == depth) { rows.append(0); } id = m_model->newId(); lastId = id; for (int column = 0; column < m_numCols; ++column) { if (m_model->m_childItems[fragmentParentIdentifier].size() <= column) { m_model->m_childItems[fragmentParentIdentifier].append(QList()); } m_model->m_items.insert(id, QString::number(id)); m_model->m_childItems[fragmentParentIdentifier][column].insert(rows[depth], id); if (column != m_numCols - 1) { id = m_model->newId(); } } rows[depth]++; } } ModelInsertAndRemoveQueuedCommand::ModelInsertAndRemoveQueuedCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { qRegisterMetaType("QModelIndex"); } void ModelInsertAndRemoveQueuedCommand::queuedBeginInsertRows(const QModelIndex &parent, int start, int end) { m_model->beginInsertRows(parent, start, end); } void ModelInsertAndRemoveQueuedCommand::queuedEndInsertRows() { m_model->endInsertRows(); } void ModelInsertAndRemoveQueuedCommand::queuedBeginRemoveRows(const QModelIndex &parent, int start, int end) { m_model->beginRemoveRows(parent, start, end); } void ModelInsertAndRemoveQueuedCommand::queuedEndRemoveRows() { m_model->endRemoveRows(); } void ModelInsertAndRemoveQueuedCommand::purgeItem(qint64 parent) { QList > childItemRows = m_model->m_childItems.value(parent); if (!childItemRows.isEmpty()) { for (int col = 0; col < m_numCols; col++) { const QList childItems = childItemRows[col]; for (qint64 item : childItems) { purgeItem(item); m_model->m_childItems[parent][col].removeOne(item); } } } m_model->m_items.remove(parent); } void ModelInsertAndRemoveQueuedCommand::doCommand() { QModelIndex parent = findIndex(m_rowNumbers); connect(this, SIGNAL(beginInsertRows(QModelIndex,int,int)), SLOT(queuedBeginInsertRows(QModelIndex,int,int)), Qt::QueuedConnection); connect(this, SIGNAL(endInsertRows()), SLOT(queuedEndInsertRows()), Qt::QueuedConnection); connect(this, SIGNAL(beginRemoveRows(QModelIndex,int,int)), SLOT(queuedBeginRemoveRows(QModelIndex,int,int)), Qt::QueuedConnection); connect(this, SIGNAL(endRemoveRows()), SLOT(queuedEndRemoveRows()), Qt::QueuedConnection); - emit beginInsertRows(parent, m_startRow, m_endRow); + Q_EMIT beginInsertRows(parent, m_startRow, m_endRow); // m_model->beginInsertRows(parent, m_startRow, m_endRow); qint64 parentId = parent.internalId(); for (int row = m_startRow; row <= m_endRow; row++) { for (int col = 0; col < m_numCols; col++) { if (m_model->m_childItems[parentId].size() <= col) { m_model->m_childItems[parentId].append(QList()); } qint64 id = m_model->newId(); QString name = QString::number(id); m_model->m_items.insert(id, name); m_model->m_childItems[parentId][col].insert(row, id); } } - emit endInsertRows(); + Q_EMIT endInsertRows(); // m_model->endInsertRows(); - emit beginRemoveRows(parent, m_startRow, m_endRow); + Q_EMIT beginRemoveRows(parent, m_startRow, m_endRow); // m_model->beginRemoveRows(parent, m_startRow, m_endRow); for (int col = 0; col < m_numCols; col++) { QList childItems = m_model->m_childItems.value(parentId).value(col); for (int row = m_startRow; row <= m_endRow; row++) { qint64 item = childItems[row]; purgeItem(item); m_model->m_childItems[parentId][col].removeOne(item); } } - emit endRemoveRows(); + Q_EMIT endRemoveRows(); // m_model->endRemoveRows(); } ModelRemoveCommand::ModelRemoveCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } void ModelRemoveCommand::doCommand() { QModelIndex parent = findIndex(m_rowNumbers); m_model->beginRemoveRows(parent, m_startRow, m_endRow); qint64 parentId = parent.internalId(); for (int col = 0; col < m_numCols; col++) { QList childItems = m_model->m_childItems.value(parentId).value(col); for (int row = m_startRow; row <= m_endRow; row++) { qint64 item = childItems[row]; purgeItem(item); m_model->m_childItems[parentId][col].removeOne(item); } } m_model->endRemoveRows(); } void ModelRemoveCommand::purgeItem(qint64 parent) { const QList > childItemRows = m_model->m_childItems.value(parent); if (!childItemRows.isEmpty()) { for (int col = 0; col < m_numCols; col++) { const QList childItems = childItemRows[col]; for (qint64 item : childItems) { purgeItem(item); m_model->m_childItems[parent][col].removeOne(item); } } } m_model->m_items.remove(parent); } ModelDataChangeCommand::ModelDataChangeCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent), m_startColumn(0) { } void ModelDataChangeCommand::doCommand() { QModelIndex parent = findIndex(m_rowNumbers); QModelIndex topLeft = m_model->index(m_startRow, m_startColumn, parent); QModelIndex bottomRight = m_model->index(m_endRow, m_numCols - 1, parent); QList > childItems = m_model->m_childItems[parent.internalId()]; for (int col = m_startColumn; col < m_startColumn + m_numCols; col++) { for (int row = m_startRow; row <= m_endRow; row++) { QString name = QString::number(m_model->newId()); m_model->m_items[childItems[col][row]] = name; } } m_model->dataChanged(topLeft, bottomRight); } ModelMoveCommand::ModelMoveCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } bool ModelMoveCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) { return m_model->beginMoveRows(srcParent, srcStart, srcEnd, destParent, destRow); } void ModelMoveCommand::doCommand() { QModelIndex srcParent = findIndex(m_rowNumbers); QModelIndex destParent = findIndex(m_destRowNumbers); if (!emitPreSignal(srcParent, m_startRow, m_endRow, destParent, m_destRow)) { return; } for (int column = 0; column < m_numCols; ++column) { const QList l = m_model->m_childItems.value(srcParent.internalId())[column].mid(m_startRow, m_endRow - m_startRow + 1); for (int i = m_startRow; i <= m_endRow; i++) { m_model->m_childItems[srcParent.internalId()][column].removeAt(m_startRow); } int d; if (m_destRow < m_startRow) { d = m_destRow; } else { if (srcParent == destParent) { d = m_destRow - (m_endRow - m_startRow + 1); } else { d = m_destRow - (m_endRow - m_startRow); } } for (const qint64 id : l) { if (!m_model->m_childItems.contains(destParent.internalId())) { m_model->m_childItems[destParent.internalId()].append(QList()); } m_model->m_childItems[destParent.internalId()][column].insert(d++, id); } } emitPostSignal(); } void ModelMoveCommand::emitPostSignal() { m_model->endMoveRows(); } ModelMoveLayoutChangeCommand::ModelMoveLayoutChangeCommand(DynamicTreeModel *model, QObject *parent): ModelMoveCommand(model, parent) { } ModelMoveLayoutChangeCommand::~ModelMoveLayoutChangeCommand() { } bool ModelMoveLayoutChangeCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) { m_model->layoutAboutToBeChanged(); const int column = 0; for (int row = srcStart; row <= srcEnd; ++row) { m_beforeMoveList << m_model->index(row, column, srcParent); } if (srcParent != destParent) { for (int row = srcEnd + 1; row < m_model->rowCount(srcParent); ++row) { m_beforeMoveList << m_model->index(row, column, srcParent); } for (int row = destRow; row < m_model->rowCount(destParent); ++row) { m_beforeMoveList << m_model->index(row, column, destParent); } } else { if (destRow < srcStart) { for (int row = destRow; row < srcStart; ++row) { m_beforeMoveList << m_model->index(row, column, srcParent); } } else { for (int row = srcStart + (srcEnd - srcStart + 1); row < destRow; ++row) { m_beforeMoveList << m_model->index(row, column, srcParent); } } } // We assume that the move was legal here. return true; } void ModelMoveLayoutChangeCommand::emitPostSignal() { int srcStart = m_startRow; int srcEnd = m_endRow; int destRow = m_destRow; // Moving indexes may affect the m_rowNumbers and m_destRowNumbers. // Instead of adjusting them programmatically, the test writer must specify them if they change. const QList sourceRowNumbers = m_endOfMoveSourceAncestors.isEmpty() ? m_rowNumbers : m_endOfMoveSourceAncestors; QModelIndex srcParent = findIndex(sourceRowNumbers); const QList destRowNumbers = m_endOfMoveDestAncestors.isEmpty() ? m_destRowNumbers : m_endOfMoveDestAncestors; QModelIndex destParent = findIndex(destRowNumbers); const int column = 0; QModelIndexList afterMoveList; if (srcParent != destParent) { for (int row = destRow; row <= (destRow + (srcEnd - srcStart)); ++row) { afterMoveList << m_model->index(row, column, destParent); } for (int row = srcStart; row < m_model->rowCount(srcParent); ++row) { afterMoveList << m_model->index(row, column, srcParent); } for (int row = destRow + (srcEnd - srcStart + 1); row < m_model->rowCount(destParent); ++row) { afterMoveList << m_model->index(row, column, destParent); } } else { if (destRow < srcStart) { for (int row = srcStart; row <= srcEnd; ++row) { afterMoveList << m_model->index(destRow + (srcStart - row), column, destParent); } } else { for (int row = srcStart; row <= srcEnd; ++row) { afterMoveList << m_model->index(destRow + (srcStart - row - 1), column, destParent); } } if (destRow < srcStart) { for (int row = destRow + 1; row <= srcStart; ++row) { afterMoveList << m_model->index(row, column, srcParent); } } else { for (int row = srcStart + (srcEnd - srcStart + 1); row < (srcStart + (destRow - srcEnd)); ++row) { afterMoveList << m_model->index(row - (srcEnd - srcStart + 1), column, srcParent); } } } m_model->changePersistentIndexList(m_beforeMoveList, afterMoveList); m_beforeMoveList.clear(); m_model->layoutChanged(); } ModelResetCommand::ModelResetCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } ModelResetCommand::~ModelResetCommand() { } void ModelResetCommand::setInitialTree(const QString &treeString) { m_treeString = treeString; } void ModelResetCommand::doCommand() { m_model->beginResetModel(); bool blocked = m_model->blockSignals(true); m_model->clear(); if (!m_treeString.isEmpty()) { ModelInsertCommand *ins = new ModelInsertCommand(m_model); ins->setStartRow(0); ins->interpret(m_treeString); ins->doCommand(); } m_model->blockSignals(blocked); m_model->endResetModel(); } ModelLayoutChangeCommand::ModelLayoutChangeCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } ModelLayoutChangeCommand::~ModelLayoutChangeCommand() { } void ModelLayoutChangeCommand::setInitialTree(const QString &treeString) { m_treeString = treeString; } void ModelLayoutChangeCommand::setPersistentChanges(const QList< ModelLayoutChangeCommand::PersistentChange > &changes) { m_changes = changes; } void ModelLayoutChangeCommand::doCommand() { m_model->layoutAboutToBeChanged(); QModelIndexList oldList; for (const PersistentChange &change : qAsConst(m_changes)) { const IndexFinder oldFinder(m_model, change.oldPath); oldList << oldFinder.getIndex(); } bool blocked = m_model->blockSignals(true); m_model->clear(); if (!m_treeString.isEmpty()) { ModelInsertCommand *ins = new ModelInsertCommand(m_model); ins->setStartRow(0); ins->interpret(m_treeString); ins->doCommand(); } QModelIndexList newList; for (const PersistentChange &change : qAsConst(m_changes)) { const IndexFinder newFinder(m_model, change.newPath); newList << newFinder.getIndex(); } m_model->changePersistentIndexList(oldList, newList); m_model->blockSignals(blocked); m_model->layoutChanged(); } diff --git a/src/core/kcheckableproxymodel.cpp b/src/core/kcheckableproxymodel.cpp index 3da28db..fec35f4 100644 --- a/src/core/kcheckableproxymodel.cpp +++ b/src/core/kcheckableproxymodel.cpp @@ -1,125 +1,125 @@ /* SPDX-FileCopyrightText: 2010 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kcheckableproxymodel.h" #include class KCheckableProxyModelPrivate { Q_DECLARE_PUBLIC(KCheckableProxyModel) KCheckableProxyModel *q_ptr; KCheckableProxyModelPrivate(KCheckableProxyModel *checkableModel) : q_ptr(checkableModel) { } QItemSelectionModel *m_itemSelectionModel = nullptr; void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); }; KCheckableProxyModel::KCheckableProxyModel(QObject *parent) : QIdentityProxyModel(parent), d_ptr(new KCheckableProxyModelPrivate(this)) { } KCheckableProxyModel::~KCheckableProxyModel() { delete d_ptr; } void KCheckableProxyModel::setSelectionModel(QItemSelectionModel *itemSelectionModel) { Q_D(KCheckableProxyModel); d->m_itemSelectionModel = itemSelectionModel; Q_ASSERT(sourceModel() ? d->m_itemSelectionModel->model() == sourceModel() : true); connect(itemSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged(QItemSelection,QItemSelection))); } QItemSelectionModel *KCheckableProxyModel::selectionModel() const { Q_D(const KCheckableProxyModel); return d->m_itemSelectionModel; } Qt::ItemFlags KCheckableProxyModel::flags(const QModelIndex &index) const { if (!index.isValid() || index.column() != 0) { return QIdentityProxyModel::flags(index); } return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable; } QVariant KCheckableProxyModel::data(const QModelIndex &index, int role) const { Q_D(const KCheckableProxyModel); if (role == Qt::CheckStateRole) { if (index.column() != 0) { return QVariant(); } if (!d->m_itemSelectionModel) { return Qt::Unchecked; } return d->m_itemSelectionModel->selection().contains(mapToSource(index)) ? Qt::Checked : Qt::Unchecked; } return QIdentityProxyModel::data(index, role); } bool KCheckableProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(KCheckableProxyModel); if (role == Qt::CheckStateRole) { if (index.column() != 0) { return false; } if (!d->m_itemSelectionModel) { return false; } Qt::CheckState state = static_cast(value.toInt()); const QModelIndex srcIndex = mapToSource(index); bool result = select(QItemSelection(srcIndex, srcIndex), state == Qt::Checked ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); - emit dataChanged(index, index); + Q_EMIT dataChanged(index, index); return result; } return QIdentityProxyModel::setData(index, value, role); } void KCheckableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { QIdentityProxyModel::setSourceModel(sourceModel); Q_ASSERT(d_ptr->m_itemSelectionModel ? d_ptr->m_itemSelectionModel->model() == sourceModel : true); } void KCheckableProxyModelPrivate::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_Q(KCheckableProxyModel); const auto lstSelected = q->mapSelectionFromSource(selected); for (const QItemSelectionRange &range : lstSelected) { - emit q->dataChanged(range.topLeft(), range.bottomRight()); + Q_EMIT q->dataChanged(range.topLeft(), range.bottomRight()); } const auto lstDeselected = q->mapSelectionFromSource(deselected); for (const QItemSelectionRange &range : lstDeselected) { - emit q->dataChanged(range.topLeft(), range.bottomRight()); + Q_EMIT q->dataChanged(range.topLeft(), range.bottomRight()); } } bool KCheckableProxyModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { Q_D(KCheckableProxyModel); d->m_itemSelectionModel->select(selection, command); return true; } #include "moc_kcheckableproxymodel.cpp" diff --git a/src/core/kconcatenaterowsproxymodel.cpp b/src/core/kconcatenaterowsproxymodel.cpp index 82e3579..5b4dde4 100644 --- a/src/core/kconcatenaterowsproxymodel.cpp +++ b/src/core/kconcatenaterowsproxymodel.cpp @@ -1,424 +1,424 @@ /* SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company SPDX-FileContributor: David Faure SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kconcatenaterowsproxymodel.h" class KConcatenateRowsProxyModelPrivate { public: KConcatenateRowsProxyModelPrivate(KConcatenateRowsProxyModel* model) : q(model) {} int computeRowsPrior(const QAbstractItemModel *sourceModel) const; QAbstractItemModel *sourceModelForRow(int row, int *sourceRow) const; void slotRowsAboutToBeInserted(const QModelIndex &, int start, int end); void slotRowsInserted(const QModelIndex &, int start, int end); void slotRowsAboutToBeRemoved(const QModelIndex &, int start, int end); void slotRowsRemoved(const QModelIndex &, int start, int end); void slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end); void slotColumnsInserted(const QModelIndex &parent, int, int); void slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void slotColumnsRemoved(const QModelIndex &parent, int, int); void slotDataChanged(const QModelIndex &from, const QModelIndex &to, const QVector &roles); void slotSourceLayoutAboutToBeChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint); void slotSourceLayoutChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint); void slotModelAboutToBeReset(); void slotModelReset(); KConcatenateRowsProxyModel *q; QList m_models; int m_rowCount = 0; // have to maintain it here since we can't compute during model destruction // for layoutAboutToBeChanged/layoutChanged QVector layoutChangePersistentIndexes; QModelIndexList proxyIndexes; }; KConcatenateRowsProxyModel::KConcatenateRowsProxyModel(QObject *parent) : QAbstractItemModel(parent), d(new KConcatenateRowsProxyModelPrivate(this)) { } KConcatenateRowsProxyModel::~KConcatenateRowsProxyModel() { } QModelIndex KConcatenateRowsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { const QAbstractItemModel *sourceModel = sourceIndex.model(); if (!sourceModel) { return {}; } int rowsPrior = d->computeRowsPrior(sourceModel); return createIndex(rowsPrior + sourceIndex.row(), sourceIndex.column()); } QModelIndex KConcatenateRowsProxyModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { return QModelIndex(); } const int row = proxyIndex.row(); int sourceRow; QAbstractItemModel *sourceModel = d->sourceModelForRow(row, &sourceRow); if (!sourceModel) { return QModelIndex(); } return sourceModel->index(sourceRow, proxyIndex.column()); } QVariant KConcatenateRowsProxyModel::data(const QModelIndex &index, int role) const { const QModelIndex sourceIndex = mapToSource(index); if (!sourceIndex.isValid()) { return QVariant(); } return sourceIndex.model()->data(sourceIndex, role); } bool KConcatenateRowsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { const QModelIndex sourceIndex = mapToSource(index); if (!sourceIndex.isValid()) { return false; } QAbstractItemModel *sourceModel = const_cast(sourceIndex.model()); return sourceModel->setData(sourceIndex, value, role); } QMap KConcatenateRowsProxyModel::itemData(const QModelIndex &proxyIndex) const { const QModelIndex sourceIndex = mapToSource(proxyIndex); if (!sourceIndex.isValid()) { return {}; } return sourceIndex.model()->itemData(sourceIndex); } Qt::ItemFlags KConcatenateRowsProxyModel::flags(const QModelIndex &index) const { const QModelIndex sourceIndex = mapToSource(index); return sourceIndex.isValid() ? sourceIndex.model()->flags(sourceIndex) : Qt::ItemFlags(); } QVariant KConcatenateRowsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (d->m_models.isEmpty()) { return QVariant(); } if (orientation == Qt::Horizontal) { return d->m_models.at(0)->headerData(section, orientation, role); } else { int sourceRow; QAbstractItemModel *sourceModel = d->sourceModelForRow(section, &sourceRow); if (!sourceModel) { return QVariant(); } return sourceModel->headerData(sourceRow, orientation, role); } } int KConcatenateRowsProxyModel::columnCount(const QModelIndex &parent) const { if (d->m_models.isEmpty()) { return 0; } if (parent.isValid()) { return 0; // flat model; } return d->m_models.at(0)->columnCount(QModelIndex()); } QHash KConcatenateRowsProxyModel::roleNames() const { if (d->m_models.isEmpty()) { return {}; } return d->m_models.at(0)->roleNames(); } QModelIndex KConcatenateRowsProxyModel::index(int row, int column, const QModelIndex &parent) const { if(row < 0) { return {}; } if(column < 0) { return {}; } int sourceRow; QAbstractItemModel *sourceModel = d->sourceModelForRow(row, &sourceRow); if (!sourceModel) { return QModelIndex(); } return mapFromSource(sourceModel->index(sourceRow, column, parent)); } QModelIndex KConcatenateRowsProxyModel::parent(const QModelIndex &) const { return QModelIndex(); // we are flat, no hierarchy } int KConcatenateRowsProxyModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; // flat model } return d->m_rowCount; } void KConcatenateRowsProxyModel::addSourceModel(QAbstractItemModel *sourceModel) { Q_ASSERT(sourceModel); Q_ASSERT(!d->m_models.contains(sourceModel)); connect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex,QVector))); connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int))); connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotRowsRemoved(QModelIndex,int,int))); connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); connect(sourceModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(slotColumnsInserted(QModelIndex,int,int))); connect(sourceModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(slotColumnsRemoved(QModelIndex,int,int))); connect(sourceModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int))); connect(sourceModel, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(slotSourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); connect(sourceModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(slotSourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); connect(sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(slotModelAboutToBeReset())); connect(sourceModel, SIGNAL(modelReset()), this, SLOT(slotModelReset())); const int newRows = sourceModel->rowCount(); if (newRows > 0) { beginInsertRows(QModelIndex(), d->m_rowCount, d->m_rowCount + newRows - 1); } d->m_rowCount += newRows; d->m_models.append(sourceModel); if (newRows > 0) { endInsertRows(); } } QList KConcatenateRowsProxyModel::sources() const { return d->m_models; } void KConcatenateRowsProxyModel::removeSourceModel(QAbstractItemModel *sourceModel) { Q_ASSERT(d->m_models.contains(sourceModel)); disconnect(sourceModel, nullptr, this, nullptr); const int rowsRemoved = sourceModel->rowCount(); const int rowsPrior = d->computeRowsPrior(sourceModel); // location of removed section if (rowsRemoved > 0) { beginRemoveRows(QModelIndex(), rowsPrior, rowsPrior + rowsRemoved - 1); } d->m_models.removeOne(sourceModel); d->m_rowCount -= rowsRemoved; if (rowsRemoved > 0) { endRemoveRows(); } } void KConcatenateRowsProxyModelPrivate::slotRowsAboutToBeInserted(const QModelIndex &, int start, int end) { const QAbstractItemModel *model = qobject_cast(q->sender()); const int rowsPrior = computeRowsPrior(model); q->beginInsertRows(QModelIndex(), rowsPrior + start, rowsPrior + end); } void KConcatenateRowsProxyModelPrivate::slotRowsInserted(const QModelIndex &, int start, int end) { m_rowCount += end - start + 1; q->endInsertRows(); } void KConcatenateRowsProxyModelPrivate::slotRowsAboutToBeRemoved(const QModelIndex &, int start, int end) { const QAbstractItemModel *model = qobject_cast(q->sender()); const int rowsPrior = computeRowsPrior(model); q->beginRemoveRows(QModelIndex(), rowsPrior + start, rowsPrior + end); } void KConcatenateRowsProxyModelPrivate::slotRowsRemoved(const QModelIndex &, int start, int end) { m_rowCount -= end - start + 1; q->endRemoveRows(); } void KConcatenateRowsProxyModelPrivate::slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end) { if (parent.isValid()) { // we are flat return; } const QAbstractItemModel *model = qobject_cast(q->sender()); if (m_models.at(0) == model) { q->beginInsertColumns(QModelIndex(), start, end); } } void KConcatenateRowsProxyModelPrivate::slotColumnsInserted(const QModelIndex &parent, int, int) { if (parent.isValid()) { // we are flat return; } const QAbstractItemModel *model = qobject_cast(q->sender()); if (m_models.at(0) == model) { q->endInsertColumns(); } } void KConcatenateRowsProxyModelPrivate::slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { if (parent.isValid()) { // we are flat return; } const QAbstractItemModel *model = qobject_cast(q->sender()); if (m_models.at(0) == model) { q->beginRemoveColumns(QModelIndex(), start, end); } } void KConcatenateRowsProxyModelPrivate::slotColumnsRemoved(const QModelIndex &parent, int, int) { if (parent.isValid()) { // we are flat return; } const QAbstractItemModel *model = qobject_cast(q->sender()); if (m_models.at(0) == model) { q->endRemoveColumns(); } } void KConcatenateRowsProxyModelPrivate::slotDataChanged(const QModelIndex &from, const QModelIndex &to, const QVector &roles) { if (!from.isValid()) { // QSFPM bug, it emits dataChanged(invalid, invalid) if a cell in a hidden column changes return; } const QModelIndex myFrom = q->mapFromSource(from); const QModelIndex myTo = q->mapFromSource(to); - emit q->dataChanged(myFrom, myTo, roles); + Q_EMIT q->dataChanged(myFrom, myTo, roles); } void KConcatenateRowsProxyModelPrivate::slotSourceLayoutAboutToBeChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint) { QList parents; parents.reserve(sourceParents.size()); for (const QPersistentModelIndex &parent : sourceParents) { if (!parent.isValid()) { parents << QPersistentModelIndex(); continue; } const QModelIndex mappedParent = q->mapFromSource(parent); Q_ASSERT(mappedParent.isValid()); parents << mappedParent; } - emit q->layoutAboutToBeChanged(parents, hint); + Q_EMIT q->layoutAboutToBeChanged(parents, hint); const QModelIndexList persistentIndexList = q->persistentIndexList(); layoutChangePersistentIndexes.reserve(persistentIndexList.size()); for (const QPersistentModelIndex &proxyPersistentIndex : persistentIndexList) { proxyIndexes << proxyPersistentIndex; Q_ASSERT(proxyPersistentIndex.isValid()); const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex); Q_ASSERT(srcPersistentIndex.isValid()); layoutChangePersistentIndexes << srcPersistentIndex; } } void KConcatenateRowsProxyModelPrivate::slotSourceLayoutChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint) { for (int i = 0; i < proxyIndexes.size(); ++i) { const QModelIndex proxyIdx = proxyIndexes.at(i); QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i)); q->changePersistentIndex(proxyIdx, newProxyIdx); } layoutChangePersistentIndexes.clear(); proxyIndexes.clear(); QList parents; parents.reserve(sourceParents.size()); for (const QPersistentModelIndex &parent : sourceParents) { if (!parent.isValid()) { parents << QPersistentModelIndex(); continue; } const QModelIndex mappedParent = q->mapFromSource(parent); Q_ASSERT(mappedParent.isValid()); parents << mappedParent; } - emit q->layoutChanged(parents, hint); + Q_EMIT q->layoutChanged(parents, hint); } void KConcatenateRowsProxyModelPrivate::slotModelAboutToBeReset() { const QAbstractItemModel *sourceModel = qobject_cast(q->sender()); Q_ASSERT(m_models.contains(const_cast(sourceModel))); const int oldRows = sourceModel->rowCount(); if (oldRows > 0) { slotRowsAboutToBeRemoved(QModelIndex(), 0, oldRows - 1); slotRowsRemoved(QModelIndex(), 0, oldRows - 1); } if (m_models.at(0) == sourceModel) { q->beginResetModel(); } } void KConcatenateRowsProxyModelPrivate::slotModelReset() { const QAbstractItemModel *sourceModel = qobject_cast(q->sender()); Q_ASSERT(m_models.contains(const_cast(sourceModel))); if (m_models.at(0) == sourceModel) { q->endResetModel(); } const int newRows = sourceModel->rowCount(); if (newRows > 0) { slotRowsAboutToBeInserted(QModelIndex(), 0, newRows - 1); slotRowsInserted(QModelIndex(), 0, newRows - 1); } } int KConcatenateRowsProxyModelPrivate::computeRowsPrior(const QAbstractItemModel *sourceModel) const { int rowsPrior = 0; for (const QAbstractItemModel *model : qAsConst(m_models)) { if (model == sourceModel) { break; } rowsPrior += model->rowCount(); } return rowsPrior; } QAbstractItemModel *KConcatenateRowsProxyModelPrivate::sourceModelForRow(int row, int *sourceRow) const { int rowCount = 0; QAbstractItemModel *selection = nullptr; for (QAbstractItemModel *model : qAsConst(m_models)) { const int subRowCount = model->rowCount(); if (rowCount + subRowCount > row) { selection = model; break; } rowCount += subRowCount; } *sourceRow = row - rowCount; return selection; } #include "moc_kconcatenaterowsproxymodel.cpp" diff --git a/src/core/kdescendantsproxymodel.cpp b/src/core/kdescendantsproxymodel.cpp index f10c524..8681cdf 100644 --- a/src/core/kdescendantsproxymodel.cpp +++ b/src/core/kdescendantsproxymodel.cpp @@ -1,993 +1,993 @@ /* 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_ignoreNextLayoutAboutToBeChanged; bool m_ignoreNextLayoutChanged; bool m_relayouting; bool m_displayAncestorData; QString m_ancestorSeparator; 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; } 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); 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; } #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(); + Q_EMIT displayAncestorDataChanged(); // send out big hammer. Everything needs to be updated. - emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1), changedRoles); + Q_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(); + Q_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); + Q_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); 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(); + Q_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; 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 (!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->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); 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->sourceModel()->hasChildren(indexAbove)) { Q_ASSERT(q->sourceModel()->rowCount(indexAbove) > 0); indexAbove = q->sourceModel()->index(q->sourceModel()->rowCount(indexAbove) - 1, column, indexAbove); } Q_ASSERT(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); Q_ASSERT(idx.isValid()); if (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(); 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(); + Q_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(); + Q_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()); 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); + Q_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/kextracolumnsproxymodel.cpp b/src/core/kextracolumnsproxymodel.cpp index 61d31c2..4701072 100644 --- a/src/core/kextracolumnsproxymodel.cpp +++ b/src/core/kextracolumnsproxymodel.cpp @@ -1,326 +1,326 @@ /* SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company SPDX-FileContributor: David Faure SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kextracolumnsproxymodel.h" #include "kitemmodels_debug.h" #include class KExtraColumnsProxyModelPrivate { Q_DECLARE_PUBLIC(KExtraColumnsProxyModel) KExtraColumnsProxyModel *const q_ptr; public: KExtraColumnsProxyModelPrivate(KExtraColumnsProxyModel *model) : q_ptr(model) { } void _ec_sourceLayoutAboutToBeChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint); void _ec_sourceLayoutChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint); // Configuration (doesn't change once source model is plugged in) QVector m_extraHeaders; // for layoutAboutToBeChanged/layoutChanged QVector layoutChangePersistentIndexes; QVector layoutChangeProxyColumns; QModelIndexList proxyIndexes; }; KExtraColumnsProxyModel::KExtraColumnsProxyModel(QObject *parent) : QIdentityProxyModel(parent), d_ptr(new KExtraColumnsProxyModelPrivate(this)) { } KExtraColumnsProxyModel::~KExtraColumnsProxyModel() { } void KExtraColumnsProxyModel::appendColumn(const QString &header) { Q_D(KExtraColumnsProxyModel); d->m_extraHeaders.append(header); } void KExtraColumnsProxyModel::removeExtraColumn(int idx) { Q_D(KExtraColumnsProxyModel); d->m_extraHeaders.remove(idx); } bool KExtraColumnsProxyModel::setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role) { Q_UNUSED(parent); Q_UNUSED(row); Q_UNUSED(extraColumn); Q_UNUSED(data); Q_UNUSED(role); return false; } void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QVector &roles) { const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), parent); - emit dataChanged(idx, idx, roles); + Q_EMIT dataChanged(idx, idx, roles); } void KExtraColumnsProxyModel::setSourceModel(QAbstractItemModel *model) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_ec_sourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); disconnect(sourceModel(), SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_ec_sourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); } QIdentityProxyModel::setSourceModel(model); if (model) { // The handling of persistent model indexes assumes mapToSource can be called for any index // This breaks for the extra column, so we'll have to do it ourselves disconnect(model, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_q_sourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); disconnect(model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_q_sourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); connect(model, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_ec_sourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); connect(model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(_ec_sourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); } } QModelIndex KExtraColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { // happens in e.g. rowCount(mapToSource(parent)) return QModelIndex(); } const int column = proxyIndex.column(); if (column >= sourceModel()->columnCount()) { qCDebug(KITEMMODELS_LOG) << "Returning invalid index in mapToSource"; return QModelIndex(); } return QIdentityProxyModel::mapToSource(proxyIndex); } QModelIndex KExtraColumnsProxyModel::buddy(const QModelIndex &proxyIndex) const { const int column = proxyIndex.column(); if (column >= sourceModel()->columnCount()) { return proxyIndex; } return QIdentityProxyModel::buddy(proxyIndex); } QModelIndex KExtraColumnsProxyModel::sibling(int row, int column, const QModelIndex &idx) const { if (row == idx.row() && column == idx.column()) { return idx; } return index(row, column, parent(idx)); } QItemSelection KExtraColumnsProxyModel::mapSelectionToSource(const QItemSelection &selection) const { QItemSelection sourceSelection; if (!sourceModel()) { return sourceSelection; } // mapToSource will give invalid index for our additional columns, so truncate the selection // to the columns known by the source model const int sourceColumnCount = sourceModel()->columnCount(); QItemSelection::const_iterator it = selection.constBegin(); const QItemSelection::const_iterator end = selection.constEnd(); for (; it != end; ++it) { Q_ASSERT(it->model() == this); QModelIndex topLeft = it->topLeft(); Q_ASSERT(topLeft.isValid()); Q_ASSERT(topLeft.model() == this); topLeft = topLeft.sibling(topLeft.row(), 0); QModelIndex bottomRight = it->bottomRight(); Q_ASSERT(bottomRight.isValid()); Q_ASSERT(bottomRight.model() == this); if (bottomRight.column() >= sourceColumnCount) { bottomRight = bottomRight.sibling(bottomRight.row(), sourceColumnCount - 1); } // This can lead to duplicate source indexes, so use merge(). const QItemSelectionRange range(mapToSource(topLeft), mapToSource(bottomRight)); QItemSelection newSelection; newSelection << range; sourceSelection.merge(newSelection, QItemSelectionModel::Select); } return sourceSelection; } int KExtraColumnsProxyModel::columnCount(const QModelIndex &parent) const { Q_D(const KExtraColumnsProxyModel); return QIdentityProxyModel::columnCount(parent) + d->m_extraHeaders.count(); } QVariant KExtraColumnsProxyModel::data(const QModelIndex &index, int role) const { Q_D(const KExtraColumnsProxyModel); const int extraCol = extraColumnForProxyColumn(index.column()); if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) { return extraColumnData(index.parent(), index.row(), extraCol, role); } return sourceModel()->data(mapToSource(index), role); } bool KExtraColumnsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(const KExtraColumnsProxyModel); const int extraCol = extraColumnForProxyColumn(index.column()); if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) { return setExtraColumnData(index.parent(), index.row(), extraCol, value, role); } return sourceModel()->setData(mapToSource(index), value, role); } Qt::ItemFlags KExtraColumnsProxyModel::flags(const QModelIndex &index) const { const int extraCol = extraColumnForProxyColumn(index.column()); if (extraCol >= 0) { // extra columns are readonly return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return sourceModel()->flags(mapToSource(index)); } bool KExtraColumnsProxyModel::hasChildren(const QModelIndex &index) const { if (index.column() > 0) { return false; } return QIdentityProxyModel::hasChildren(index); } QVariant KExtraColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_D(const KExtraColumnsProxyModel); if (orientation == Qt::Horizontal) { const int extraCol = extraColumnForProxyColumn(section); if (extraCol >= 0) { // Only text is supported, in headers for extra columns if (role == Qt::DisplayRole) { return d->m_extraHeaders.at(extraCol); } return QVariant(); } } return QIdentityProxyModel::headerData(section, orientation, role); } QModelIndex KExtraColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const { const int extraCol = extraColumnForProxyColumn(column); if (extraCol >= 0) { // We store the internal pointer of the index for column 0 in the proxy index for extra columns. // This will be useful in the parent method. return createIndex(row, column, QIdentityProxyModel::index(row, 0, parent).internalPointer()); } return QIdentityProxyModel::index(row, column, parent); } QModelIndex KExtraColumnsProxyModel::parent(const QModelIndex &child) const { const int extraCol = extraColumnForProxyColumn(child.column()); if (extraCol >= 0) { // Create an index for column 0 and use that to get the parent. const QModelIndex proxySibling = createIndex(child.row(), 0, child.internalPointer()); return QIdentityProxyModel::parent(proxySibling); } return QIdentityProxyModel::parent(child); } int KExtraColumnsProxyModel::extraColumnForProxyColumn(int proxyColumn) const { const int sourceColumnCount = sourceModel()->columnCount(); if (proxyColumn >= sourceColumnCount) { return proxyColumn - sourceColumnCount; } return -1; } int KExtraColumnsProxyModel::proxyColumnForExtraColumn(int extraColumn) const { return sourceModel()->columnCount() + extraColumn; } void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutAboutToBeChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint) { Q_Q(KExtraColumnsProxyModel); QList parents; parents.reserve(sourceParents.size()); for (const QPersistentModelIndex &parent : sourceParents) { if (!parent.isValid()) { parents << QPersistentModelIndex(); continue; } const QModelIndex mappedParent = q->mapFromSource(parent); Q_ASSERT(mappedParent.isValid()); parents << mappedParent; } - emit q->layoutAboutToBeChanged(parents, hint); + Q_EMIT q->layoutAboutToBeChanged(parents, hint); const QModelIndexList persistentIndexList = q->persistentIndexList(); layoutChangePersistentIndexes.reserve(persistentIndexList.size()); layoutChangeProxyColumns.reserve(persistentIndexList.size()); for (QPersistentModelIndex proxyPersistentIndex : persistentIndexList) { proxyIndexes << proxyPersistentIndex; Q_ASSERT(proxyPersistentIndex.isValid()); const int column = proxyPersistentIndex.column(); layoutChangeProxyColumns << column; if (column >= q->sourceModel()->columnCount()) { proxyPersistentIndex = proxyPersistentIndex.sibling(proxyPersistentIndex.row(), 0); } const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex); Q_ASSERT(srcPersistentIndex.isValid()); layoutChangePersistentIndexes << srcPersistentIndex; } } void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint) { Q_Q(KExtraColumnsProxyModel); for (int i = 0; i < proxyIndexes.size(); ++i) { const QModelIndex proxyIdx = proxyIndexes.at(i); QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i)); if (proxyIdx.column() >= q->sourceModel()->columnCount()) { newProxyIdx = newProxyIdx.sibling(newProxyIdx.row(), layoutChangeProxyColumns.at(i)); } q->changePersistentIndex(proxyIdx, newProxyIdx); } layoutChangePersistentIndexes.clear(); layoutChangeProxyColumns.clear(); proxyIndexes.clear(); QList parents; parents.reserve(sourceParents.size()); for (const QPersistentModelIndex &parent : sourceParents) { if (!parent.isValid()) { parents << QPersistentModelIndex(); continue; } const QModelIndex mappedParent = q->mapFromSource(parent); Q_ASSERT(mappedParent.isValid()); parents << mappedParent; } - emit q->layoutChanged(parents, hint); + Q_EMIT q->layoutChanged(parents, hint); } #include "moc_kextracolumnsproxymodel.cpp" diff --git a/src/core/knumbermodel.cpp b/src/core/knumbermodel.cpp index 748d48b..18ebe97 100644 --- a/src/core/knumbermodel.cpp +++ b/src/core/knumbermodel.cpp @@ -1,136 +1,136 @@ /* SPDX-FileCopyrightText: 2018 David Edmundson SPDX-License-Identifier: LGPL-2.0-or-later */ #include "knumbermodel.h" #include #include #include class KNumberModelPrivate { public: qreal minimumValue = 0.0; qreal maximumValue = 0.0; qreal stepSize = 1.0; QLocale::NumberOptions formattingOptions = QLocale::DefaultNumberOptions; }; KNumberModel::KNumberModel(QObject *parent): QAbstractListModel(parent), d(new KNumberModelPrivate) {} KNumberModel::~KNumberModel() {} void KNumberModel::setMinimumValue(qreal minimumValue) { if (minimumValue == d->minimumValue) { return; } beginResetModel(); d->minimumValue = minimumValue; endResetModel(); - emit minimumValueChanged(); + Q_EMIT minimumValueChanged(); } qreal KNumberModel::minimumValue() const { return d->minimumValue; } void KNumberModel::setMaximumValue(qreal maximumValue) { if (maximumValue == d->maximumValue) { return; } beginResetModel(); d->maximumValue = maximumValue; endResetModel(); - emit maximumValueChanged(); + Q_EMIT maximumValueChanged(); } qreal KNumberModel::maximumValue() const { return d->maximumValue; } void KNumberModel::setStepSize(qreal stepSize) { if (stepSize == d->stepSize) { return; } beginResetModel(); d->stepSize = stepSize; endResetModel(); - emit stepSizeChanged(); + Q_EMIT stepSizeChanged(); } qreal KNumberModel::stepSize() const { return d->stepSize; } void KNumberModel::setFormattingOptions(QLocale::NumberOptions formattingOptions) { if (d->formattingOptions == formattingOptions) { return; } d->formattingOptions = formattingOptions; if (rowCount() == 0) { return; } dataChanged(index(0, 0, QModelIndex()), index(rowCount(), 0, QModelIndex()), QVector{DisplayRole}); - emit formattingOptionsChanged(); + Q_EMIT formattingOptionsChanged(); } QLocale::NumberOptions KNumberModel::formattingOptions() const { return d->formattingOptions; } qreal KNumberModel::value(const QModelIndex &index) const { if (!index.isValid()) { return 0.0; } return d->minimumValue + d->stepSize * index.row(); } int KNumberModel::rowCount(const QModelIndex &index) const { if (index.parent().isValid()) { return 0; } if (d->stepSize == 0) { return 1; } //1 initial entry (the minimumValue) + the number of valid steps afterwards return 1 + std::max(0, qFloor((d->maximumValue - d->minimumValue) / d->stepSize)); } QVariant KNumberModel::data(const QModelIndex &index, int role) const { switch(role) { case KNumberModel::DisplayRole: { auto locale = QLocale::system(); locale.setNumberOptions(d->formattingOptions); return QVariant(locale.toString(value(index))); } case KNumberModel::ValueRole: return QVariant(value(index)); } return QVariant(); } QHash KNumberModel::roleNames() const { return {{KNumberModel::DisplayRole, QByteArrayLiteral("display")}, {KNumberModel::ValueRole, QByteArrayLiteral("value")}}; } diff --git a/src/core/kselectionproxymodel.cpp b/src/core/kselectionproxymodel.cpp index 0d3bc2e..88bcbda 100644 --- a/src/core/kselectionproxymodel.cpp +++ b/src/core/kselectionproxymodel.cpp @@ -1,2578 +1,2578 @@ /* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-FileCopyrightText: 2016 Ableton AG SPDX-FileContributor: Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kselectionproxymodel.h" #include #include #include #include "kmodelindexproxymapper.h" #include "kbihash_p.h" #include "kvoidpointerfactory_p.h" typedef KBiHash SourceProxyIndexMapping; typedef KBiHash ParentMapping; typedef KHash2Map SourceIndexProxyRowMapping; /** Return true if @p idx is a descendant of one of the indexes in @p list. Note that this returns false if @p list contains @p idx. */ template bool isDescendantOf(const QList &list, const QModelIndex &idx) { if (!idx.isValid()) { return false; } if (list.contains(idx)) { return false; } QModelIndex parent = idx.parent(); while (parent.isValid()) { if (list.contains(parent)) { return true; } parent = parent.parent(); } return false; } static bool isDescendantOf(const QItemSelection &selection, const QModelIndex &descendant) { if (!descendant.isValid()) { return false; } if (selection.contains(descendant)) { return false; } QModelIndex parent = descendant.parent(); while (parent.isValid()) { if (selection.contains(parent)) { return true; } parent = parent.parent(); } return false; } static bool isDescendantOf(const QItemSelectionRange &range, const QModelIndex &descendant) { if (!descendant.isValid()) { return false; } if (range.contains(descendant)) { return false; } QModelIndex parent = descendant.parent(); while (parent.isValid()) { if (range.contains(parent)) { return true; } parent = parent.parent(); } return false; } static int _getRootListRow(const QList &rootAncestors, const QModelIndex &index) { QModelIndex commonParent = index; QModelIndex youngestAncestor; int firstCommonParent = -1; int bestParentRow = -1; while (commonParent.isValid()) { youngestAncestor = commonParent; commonParent = commonParent.parent(); for (int i = 0; i < rootAncestors.size(); ++i) { const QModelIndexList ancestorList = rootAncestors.at(i); const int parentRow = ancestorList.indexOf(commonParent); if (parentRow < 0) { continue; } if (parentRow > bestParentRow) { firstCommonParent = i; bestParentRow = parentRow; } } if (firstCommonParent >= 0) { break; } } // If @p list is non-empty, the invalid QModelIndex() will at least be found in ancestorList. Q_ASSERT(firstCommonParent >= 0); const QModelIndexList firstAnsList = rootAncestors.at(firstCommonParent); const QModelIndex eldestSibling = firstAnsList.value(bestParentRow + 1); if (eldestSibling.isValid()) { // firstCommonParent is a sibling of one of the ancestors of @p index. // It is the first index to share a common parent with one of the ancestors of @p index. if (eldestSibling.row() >= youngestAncestor.row()) { return firstCommonParent; } } int siblingOffset = 1; // The same commonParent might be common to several root indexes. // If this is the last in the list, it's the only match. We instruct the model // to insert the new index after it ( + siblingOffset). if (rootAncestors.size() <= firstCommonParent + siblingOffset) { return firstCommonParent + siblingOffset; } // A // - B // - C // - D // - E // F // // F is selected, then C then D. When inserting D into the model, the commonParent is B (the parent of C). // The next existing sibling of B is F (in the proxy model). bestParentRow will then refer to an index on // the level of a child of F (which doesn't exist - Boom!). If it doesn't exist, then we've already found // the place to insert D QModelIndexList ansList = rootAncestors.at(firstCommonParent + siblingOffset); if (ansList.size() <= bestParentRow) { return firstCommonParent + siblingOffset; } QModelIndex nextParent = ansList.at(bestParentRow); while (nextParent == commonParent) { if (ansList.size() < bestParentRow + 1) // If the list is longer, it means that at the end of it is a descendant of the new index. // We insert the ancestors items first in that case. { break; } const QModelIndex nextSibling = ansList.value(bestParentRow + 1); if (!nextSibling.isValid()) { continue; } if (youngestAncestor.row() <= nextSibling.row()) { break; } siblingOffset++; if (rootAncestors.size() <= firstCommonParent + siblingOffset) { break; } ansList = rootAncestors.at(firstCommonParent + siblingOffset); // In the scenario above, E is selected after D, causing this loop to be entered, // and requiring a similar result if the next sibling in the proxy model does not have children. if (ansList.size() <= bestParentRow) { break; } nextParent = ansList.at(bestParentRow); } return firstCommonParent + siblingOffset; } /** Determines the correct location to insert @p index into @p list. */ template static int getRootListRow(const QList &list, const QModelIndex &index) { if (!index.isValid()) { return -1; } if (list.isEmpty()) { return 0; } // What's going on? // Consider a tree like // // A // - B // - - C // - - - D // - E // - F // - - G // - - - H // - I // - - J // - K // // If D, E and J are already selected, and H is newly selected, we need to put H between E and J in the proxy model. // To figure that out, we create a list for each already selected index of its ancestors. Then, // we climb the ancestors of H until we reach an index with siblings which have a descendant // selected (F above has siblings B, E and I which have descendants which are already selected). // Those child indexes are traversed to find the right sibling to put F beside. // // i.e., new items are inserted in the expected location. QList rootAncestors; for (const QModelIndex &root : list) { QModelIndexList ancestors; ancestors << root; QModelIndex parent = root.parent(); while (parent.isValid()) { ancestors.prepend(parent); parent = parent.parent(); } ancestors.prepend(QModelIndex()); rootAncestors << ancestors; } return _getRootListRow(rootAncestors, index); } /** Returns a selection in which no descendants of selected indexes are also themselves selected. For example, @code A - B C D @endcode If A, B and D are selected in @p selection, the returned selection contains only A and D. */ static QItemSelection getRootRanges(const QItemSelection &_selection) { QItemSelection rootSelection; QItemSelection selection = _selection; QList::iterator it = selection.begin(); while (it != selection.end()) { if (!it->topLeft().parent().isValid()) { rootSelection.append(*it); it = selection.erase(it); } else { ++it; } } it = selection.begin(); const QList::iterator end = selection.end(); while (it != end) { const QItemSelectionRange range = *it; it = selection.erase(it); if (isDescendantOf(rootSelection, range.topLeft()) || isDescendantOf(selection, range.topLeft())) { continue; } rootSelection << range; } return rootSelection; } /** */ struct RangeLessThan { bool operator()(const QItemSelectionRange &left, const QItemSelectionRange &right) const { if (right.model() == left.model()) { // parent has to be calculated, so we only do so once. const QModelIndex topLeftParent = left.parent(); const QModelIndex otherTopLeftParent = right.parent(); if (topLeftParent == otherTopLeftParent) { if (right.top() == left.top()) { if (right.left() == left.left()) { if (right.bottom() == left.bottom()) { return left.right() < right.right(); } return left.bottom() < right.bottom(); } return left.left() < right.left(); } return left.top() < right.top(); } return topLeftParent < otherTopLeftParent; } return left.model() < right.model(); } }; static QItemSelection stableNormalizeSelection(const QItemSelection &selection) { if (selection.size() <= 1) { return selection; } QItemSelection::const_iterator it = selection.begin(); const QItemSelection::const_iterator end = selection.end(); Q_ASSERT(it != end); QItemSelection::const_iterator scout = it + 1; QItemSelection result; while (scout != end) { Q_ASSERT(it != end); int bottom = it->bottom(); while (scout != end && it->parent() == scout->parent() && bottom + 1 == scout->top()) { bottom = scout->bottom(); ++scout; } if (bottom != it->bottom()) { const QModelIndex topLeft = it->topLeft(); result << QItemSelectionRange(topLeft, topLeft.sibling(bottom, it->right())); } Q_ASSERT(it != scout); if (scout == end) { break; } if (it + 1 == scout) { result << *it; } it = scout; ++scout; if (scout == end) { result << *it; } } return result; } static QItemSelection kNormalizeSelection(QItemSelection selection) { if (selection.size() <= 1) { return selection; } // KSelectionProxyModel has a strong assumption that // the views always select rows, so usually the // @p selection here contains ranges that span all // columns. However, if a QSortFilterProxyModel // is used too, it splits up the complete ranges into // one index per range. That confuses the data structures // held by this proxy (which keeps track of indexes in the // first column). As this proxy already assumes that if the // zeroth column is selected, then its entire row is selected, // we can safely remove the ranges which do not include // column 0 without a loss of functionality. QItemSelection::iterator i = selection.begin(); while (i != selection.end()) { if (i->left() > 0) { i = selection.erase(i); } else { ++i; } } RangeLessThan lt; std::sort(selection.begin(), selection.end(), lt); return stableNormalizeSelection(selection); } class KSelectionProxyModelPrivate { public: KSelectionProxyModelPrivate(KSelectionProxyModel *model) : q_ptr(model), m_indexMapper(nullptr), m_startWithChildTrees(false), m_omitChildren(false), m_omitDescendants(false), m_includeAllSelected(false), m_rowsInserted(false), m_rowsRemoved(false), m_recreateFirstChildMappingOnRemoval(false), m_rowsMoved(false), m_resetting(false), m_sourceModelResetting(false), m_doubleResetting(false), m_layoutChanging(false), m_ignoreNextLayoutAboutToBeChanged(false), m_ignoreNextLayoutChanged(false), m_selectionModel(nullptr), m_filterBehavior(KSelectionProxyModel::InvalidBehavior) { } Q_DECLARE_PUBLIC(KSelectionProxyModel) KSelectionProxyModel *const q_ptr; // A unique id is generated for each parent. It is used for the internalPointer of its children in the proxy // This is used to store a unique id for QModelIndexes in the proxy which have children. // If an index newly gets children it is added to this hash. If its last child is removed it is removed from this map. // If this map contains an index, that index hasChildren(). This hash is populated when new rows are inserted in the // source model, or a new selection is made. mutable ParentMapping m_parentIds; // This mapping maps indexes with children in the source to indexes with children in the proxy. // The order of indexes in this list is not relevant. mutable SourceProxyIndexMapping m_mappedParents; KVoidPointerFactory<> m_voidPointerFactory; /** Keeping Persistent indexes from this model in this model breaks in certain situations such as after source insert, but before calling endInsertRows in this model. In such a state, the persistent indexes are not updated, but the methods assume they are already uptodate. Instead of using persistentindexes for proxy indexes in m_mappedParents, we maintain them ourselves with this method. m_mappedParents and m_parentIds are affected. @p parent and @p start refer to the proxy model. Any rows >= @p start will be updated. @p offset is the amount that affected indexes will be changed. */ void updateInternalIndexes(const QModelIndex &parent, int start, int offset); /** * Updates stored indexes in the proxy. Any proxy row >= @p start is changed by @p offset. * * This is only called to update indexes in the top level of the proxy. Most commonly that is * * m_mappedParents, m_parentIds and m_mappedFirstChildren are affected. */ void updateInternalTopIndexes(int start, int offset); void updateFirstChildMapping(const QModelIndex &parent, int offset); bool isFlat() const { return m_omitChildren || (m_omitDescendants && m_startWithChildTrees); } /** * Tries to ensure that @p parent is a mapped parent in the proxy. * Returns true if parent is mappable in the model, and false otherwise. */ bool ensureMappable(const QModelIndex &parent) const; bool parentIsMappable(const QModelIndex &parent) const { return parentAlreadyMapped(parent) || m_rootIndexList.contains(parent); } /** * Maps @p parent to source if it is already mapped, and otherwise returns an invalid QModelIndex. */ QModelIndex mapFromSource(const QModelIndex &parent) const; /** Creates mappings in m_parentIds and m_mappedParents between the source and the proxy. This is not recursive */ void createParentMappings(const QModelIndex &parent, int start, int end) const; void createFirstChildMapping(const QModelIndex &parent, int proxyRow) const; bool firstChildAlreadyMapped(const QModelIndex &firstChild) const; bool parentAlreadyMapped(const QModelIndex &parent) const; void removeFirstChildMappings(int start, int end); void removeParentMappings(const QModelIndex &parent, int start, int end); /** Given a QModelIndex in the proxy, return the corresponding QModelIndex in the source. This method works only if the index has children in the proxy model which already has a mapping from the source. This means that if the proxy is a flat list, this method will always return QModelIndex(). Additionally, it means that m_mappedParents is not populated automatically and must be populated manually. No new mapping is created by this method. */ QModelIndex mapParentToSource(const QModelIndex &proxyParent) const; /** Given a QModelIndex in the source model, return the corresponding QModelIndex in the proxy. This method works only if the index has children in the proxy model which already has a mapping from the source. No new mapping is created by this method. */ QModelIndex mapParentFromSource(const QModelIndex &sourceParent) const; QModelIndex mapTopLevelToSource(int row, int column) const; QModelIndex mapTopLevelFromSource(const QModelIndex &sourceIndex) const; QModelIndex createTopLevelIndex(int row, int column) const; int topLevelRowCount() const; void *parentId(const QModelIndex &proxyParent) const { return m_parentIds.rightToLeft(proxyParent); } QModelIndex parentForId(void *id) const { return m_parentIds.leftToRight(id); } // Only populated if m_startWithChildTrees. mutable SourceIndexProxyRowMapping m_mappedFirstChildren; // Source list is the selection in the source model. QList m_rootIndexList; KModelIndexProxyMapper *m_indexMapper; QPair beginRemoveRows(const QModelIndex &parent, int start, int end) const; QPair beginInsertRows(const QModelIndex &parent, int start, int end) const; void endRemoveRows(const QModelIndex &sourceParent, int proxyStart, int proxyEnd); void endInsertRows(const QModelIndex &parent, int start, int end); 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 sourceModelReset(); void sourceLayoutAboutToBeChanged(); void sourceLayoutChanged(); void emitContinuousRanges(const QModelIndex &sourceFirst, const QModelIndex &sourceLast, const QModelIndex &proxyFirst, const QModelIndex &proxyLast); void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void removeSelectionFromProxy(const QItemSelection &selection); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void sourceModelDestroyed(); void resetInternalData(); bool rootWillBeRemoved(const QItemSelection &selection, const QModelIndex &root); /** When items are inserted or removed in the m_startWithChildTrees configuration, this method helps find the startRow for use emitting the signals from the proxy. */ int getProxyInitialRow(const QModelIndex &parent) const; /** If m_startWithChildTrees is true, this method returns the row in the proxy model to insert newIndex items. This is a special case because the items above rootListRow in the list are not in the model, but their children are. Those children must be counted. If m_startWithChildTrees is false, this method returns @p rootListRow. */ int getTargetRow(int rootListRow); /** Inserts the indexes in @p list into the proxy model. */ void insertSelectionIntoProxy(const QItemSelection &selection); bool m_startWithChildTrees; bool m_omitChildren; bool m_omitDescendants; bool m_includeAllSelected; bool m_rowsInserted; bool m_rowsRemoved; bool m_recreateFirstChildMappingOnRemoval; QPair m_proxyRemoveRows; bool m_rowsMoved; bool m_resetting; bool m_sourceModelResetting; bool m_doubleResetting; bool m_layoutChanging; bool m_ignoreNextLayoutAboutToBeChanged; bool m_ignoreNextLayoutChanged; QPointer m_selectionModel; KSelectionProxyModel::FilterBehavior m_filterBehavior; QList m_layoutChangePersistentIndexes; QModelIndexList m_proxyIndexes; struct PendingSelectionChange { PendingSelectionChange() {} PendingSelectionChange(const QItemSelection &selected_, const QItemSelection &deselected_) : selected(selected_), deselected(deselected_) { } QItemSelection selected; QItemSelection deselected; }; QVector m_pendingSelectionChanges; QMetaObject::Connection selectionModelModelAboutToBeResetConnection; QMetaObject::Connection selectionModelModelResetConnection; }; void KSelectionProxyModelPrivate::emitContinuousRanges(const QModelIndex &sourceFirst, const QModelIndex &sourceLast, const QModelIndex &proxyFirst, const QModelIndex &proxyLast) { Q_Q(KSelectionProxyModel); Q_ASSERT(sourceFirst.model() == q->sourceModel()); Q_ASSERT(sourceLast.model() == q->sourceModel()); Q_ASSERT(proxyFirst.model() == q); Q_ASSERT(proxyLast.model() == q); const int proxyRangeSize = proxyLast.row() - proxyFirst.row(); const int sourceRangeSize = sourceLast.row() - sourceFirst.row(); if (proxyRangeSize == sourceRangeSize) { - emit q->dataChanged(proxyFirst, proxyLast); + Q_EMIT q->dataChanged(proxyFirst, proxyLast); return; } // TODO: Loop to skip descendant ranges. // int lastRow; // // const QModelIndex sourceHalfWay = sourceFirst.sibling(sourceFirst.row() + (sourceRangeSize / 2)); // const QModelIndex proxyHalfWay = proxyFirst.sibling(proxyFirst.row() + (proxyRangeSize / 2)); // const QModelIndex mappedSourceHalfway = q->mapToSource(proxyHalfWay); // // const int halfProxyRange = mappedSourceHalfway.row() - proxyFirst.row(); // const int halfSourceRange = sourceHalfWay.row() - sourceFirst.row(); // // if (proxyRangeSize == sourceRangeSize) // { -// emit q->dataChanged(proxyFirst, proxyLast.sibling(proxyFirst.row() + proxyRangeSize, proxyLast.column())); +// Q_EMIT q->dataChanged(proxyFirst, proxyLast.sibling(proxyFirst.row() + proxyRangeSize, proxyLast.column())); // return; // } - emit q->dataChanged(proxyFirst, proxyLast); + Q_EMIT q->dataChanged(proxyFirst, proxyLast); } void KSelectionProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { Q_Q(KSelectionProxyModel); Q_ASSERT(topLeft.model() == q->sourceModel()); Q_ASSERT(bottomRight.model() == q->sourceModel()); const QModelIndex sourceRangeParent = topLeft.parent(); if (!sourceRangeParent.isValid() && m_startWithChildTrees && !m_rootIndexList.contains(sourceRangeParent)) { return; } const QModelIndex proxyTopLeft = q->mapFromSource(topLeft); const QModelIndex proxyBottomRight = q->mapFromSource(bottomRight); const QModelIndex proxyRangeParent = proxyTopLeft.parent(); if (!m_omitChildren && m_omitDescendants && m_startWithChildTrees && m_includeAllSelected) { // ChildrenOfExactSelection if (proxyTopLeft.isValid()) { emitContinuousRanges(topLeft, bottomRight, proxyTopLeft, proxyBottomRight); } return; } if ((m_omitChildren && !m_startWithChildTrees && m_includeAllSelected) || (!proxyRangeParent.isValid() && !m_startWithChildTrees)) { // Exact selection and SubTreeRoots and SubTrees in top level // Emit continuous ranges. QList changedRows; for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { const QModelIndex index = q->sourceModel()->index(row, topLeft.column(), topLeft.parent()); const int idx = m_rootIndexList.indexOf(index); if (idx != -1) { changedRows.append(idx); } } if (changedRows.isEmpty()) { return; } int first = changedRows.first(); int previous = first; QList::const_iterator it = changedRows.constBegin(); const QList::const_iterator end = changedRows.constEnd(); for (; it != end; ++it) { if (*it == previous + 1) { ++previous; } else { const QModelIndex _top = q->index(first, topLeft.column()); const QModelIndex _bottom = q->index(previous, bottomRight.column()); - emit q->dataChanged(_top, _bottom); + Q_EMIT q->dataChanged(_top, _bottom); previous = first = *it; } } if (first != previous) { const QModelIndex _top = q->index(first, topLeft.column()); const QModelIndex _bottom = q->index(previous, bottomRight.column()); - emit q->dataChanged(_top, _bottom); + Q_EMIT q->dataChanged(_top, _bottom); } return; } if (proxyRangeParent.isValid()) { if (m_omitChildren && !m_startWithChildTrees && !m_includeAllSelected) // SubTreeRoots { return; } if (!proxyTopLeft.isValid()) { return; } // SubTrees and SubTreesWithoutRoots - emit q->dataChanged(proxyTopLeft, proxyBottomRight); + Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight); return; } if (m_startWithChildTrees && !m_omitChildren && !m_includeAllSelected && !m_omitDescendants) { // SubTreesWithoutRoots if (proxyTopLeft.isValid()) { - emit q->dataChanged(proxyTopLeft, proxyBottomRight); + Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight); } return; } } void KSelectionProxyModelPrivate::sourceLayoutAboutToBeChanged() { Q_Q(KSelectionProxyModel); if (m_ignoreNextLayoutAboutToBeChanged) { m_ignoreNextLayoutAboutToBeChanged = false; return; } if (m_rootIndexList.isEmpty()) { return; } - emit q->layoutAboutToBeChanged(); + Q_EMIT q->layoutAboutToBeChanged(); QItemSelection selection; for (const QModelIndex &rootIndex : qAsConst(m_rootIndexList)) { // This will be optimized later. - emit q->rootIndexAboutToBeRemoved(rootIndex); + Q_EMIT q->rootIndexAboutToBeRemoved(rootIndex); selection.append(QItemSelectionRange(rootIndex, rootIndex)); } selection = kNormalizeSelection(selection); - emit q->rootSelectionAboutToBeRemoved(selection); + Q_EMIT q->rootSelectionAboutToBeRemoved(selection); 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; } m_rootIndexList.clear(); } void KSelectionProxyModelPrivate::sourceLayoutChanged() { Q_Q(KSelectionProxyModel); if (m_ignoreNextLayoutChanged) { m_ignoreNextLayoutChanged = false; return; } if (!m_selectionModel || !m_selectionModel->hasSelection()) { return; } // Handling this signal is slow. // The problem is that anything can happen between emissions of layoutAboutToBeChanged and layoutChanged. // We can't assume anything is the same about the structure anymore. items have been sorted, items which // were parents before are now not, items which were not parents before now are, items which used to be the // first child are now the Nth child and items which used to be the Nth child are now the first child. // We effectively can't update our mapping because we don't have enough information to update everything. // The only way we would have is if we take a persistent index of the entire source model // on sourceLayoutAboutToBeChanged and then examine it here. That would be far too expensive. // Instead we just have to clear the entire mapping and recreate it. m_rootIndexList.clear(); m_mappedFirstChildren.clear(); m_mappedParents.clear(); m_parentIds.clear(); m_resetting = true; m_layoutChanging = true; selectionChanged(m_selectionModel->selection(), QItemSelection()); m_resetting = false; m_layoutChanging = false; 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(); + Q_EMIT q->layoutChanged(); } void KSelectionProxyModelPrivate::resetInternalData() { m_rootIndexList.clear(); m_layoutChangePersistentIndexes.clear(); m_proxyIndexes.clear(); m_mappedParents.clear(); m_parentIds.clear(); m_mappedFirstChildren.clear(); m_voidPointerFactory.clear(); } void KSelectionProxyModelPrivate::sourceModelDestroyed() { // There is very little we can do here. resetInternalData(); m_resetting = false; m_sourceModelResetting = false; } void KSelectionProxyModelPrivate::sourceModelAboutToBeReset() { Q_Q(KSelectionProxyModel); // We might be resetting as a result of the selection source model resetting. // If so we don't want to emit // sourceModelAboutToBeReset // sourceModelAboutToBeReset // sourceModelReset // sourceModelReset // So we ensure that we just emit one. if (m_resetting) { // If both the source model and the selection source model are reset, // We want to begin our reset before the first one is reset and end // it after the second one is reset. m_doubleResetting = true; return; } q->beginResetModel(); m_resetting = true; m_sourceModelResetting = true; } void KSelectionProxyModelPrivate::sourceModelReset() { Q_Q(KSelectionProxyModel); if (m_doubleResetting) { m_doubleResetting = false; return; } resetInternalData(); m_sourceModelResetting = false; m_resetting = false; selectionChanged(m_selectionModel->selection(), QItemSelection()); q->endResetModel(); } int KSelectionProxyModelPrivate::getProxyInitialRow(const QModelIndex &parent) const { Q_ASSERT(m_rootIndexList.contains(parent)); // The difficulty here is that parent and parent.parent() might both be in the m_rootIndexList. // - A // - B // - - C // - - D // - - - E // Consider that B and D are selected. The proxy model is: // - C // - D // - E // Then D gets a new child at 0. In that case we require adding F between D and E. // Consider instead that D gets removed. Then @p parent will be B. Q_Q(const KSelectionProxyModel); Q_ASSERT(parent.model() == q->sourceModel()); int parentPosition = m_rootIndexList.indexOf(parent); QModelIndex parentAbove; // If parentPosition == 0, then parent.parent() is not also in the model. (ordering is preserved) while (parentPosition > 0) { parentPosition--; parentAbove = m_rootIndexList.at(parentPosition); Q_ASSERT(parentAbove.isValid()); int rows = q->sourceModel()->rowCount(parentAbove); if (rows > 0) { QModelIndex sourceIndexAbove = q->sourceModel()->index(rows - 1, 0, parentAbove); Q_ASSERT(sourceIndexAbove.isValid()); QModelIndex proxyChildAbove = mapFromSource(sourceIndexAbove); Q_ASSERT(proxyChildAbove.isValid()); return proxyChildAbove.row() + 1; } } return 0; } void KSelectionProxyModelPrivate::updateFirstChildMapping(const QModelIndex &parent, int offset) { Q_Q(KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); static const int column = 0; static const int row = 0; const QPersistentModelIndex srcIndex = q->sourceModel()->index(row, column, parent); const QPersistentModelIndex previousFirstChild = q->sourceModel()->index(offset, column, parent); SourceIndexProxyRowMapping::left_iterator it = m_mappedFirstChildren.findLeft(previousFirstChild); if (it == m_mappedFirstChildren.leftEnd()) { return; } Q_ASSERT(srcIndex.isValid()); const int proxyRow = it.value(); Q_ASSERT(proxyRow >= 0); m_mappedFirstChildren.eraseLeft(it); // The proxy row in the mapping has already been updated by the offset in updateInternalTopIndexes // so we restore it by applying the reverse. m_mappedFirstChildren.insert(srcIndex, proxyRow - offset); } QPair< int, int > KSelectionProxyModelPrivate::beginInsertRows(const QModelIndex &parent, int start, int end) const { const QModelIndex proxyParent = mapFromSource(parent); if (!proxyParent.isValid()) { if (!m_startWithChildTrees) { return qMakePair(-1, -1); } if (!m_rootIndexList.contains(parent)) { return qMakePair(-1, -1); } } if (!m_startWithChildTrees) { // SubTrees if (proxyParent.isValid()) { return qMakePair(start, end); } return qMakePair(-1, -1); } if (!m_includeAllSelected && proxyParent.isValid()) { // SubTreesWithoutRoots deeper than topLevel return qMakePair(start, end); } if (!m_rootIndexList.contains(parent)) { return qMakePair(-1, -1); } const int proxyStartRow = getProxyInitialRow(parent) + start; return qMakePair(proxyStartRow, proxyStartRow + (end - start)); } void KSelectionProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_Q(KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); if (!m_selectionModel || !m_selectionModel->hasSelection()) { return; } if (m_omitChildren) // ExactSelection and SubTreeRoots { return; } // topLevel insertions can be ignored because topLevel items would need to be selected to affect the proxy. if (!parent.isValid()) { return; } QPair pair = beginInsertRows(parent, start, end); if (pair.first == -1) { return; } const QModelIndex proxyParent = m_startWithChildTrees ? QModelIndex() : mapFromSource(parent); m_rowsInserted = true; q->beginInsertRows(proxyParent, pair.first, pair.second); } void KSelectionProxyModelPrivate::endInsertRows(const QModelIndex &parent, int start, int end) { Q_Q(const KSelectionProxyModel); const QModelIndex proxyParent = mapFromSource(parent); const int offset = end - start + 1; const bool isNewParent = (q->sourceModel()->rowCount(parent) == offset); if (m_startWithChildTrees && m_rootIndexList.contains(parent)) { const int proxyInitialRow = getProxyInitialRow(parent); Q_ASSERT(proxyInitialRow >= 0); const int proxyStartRow = proxyInitialRow + start; updateInternalTopIndexes(proxyStartRow, offset); if (isNewParent) { createFirstChildMapping(parent, proxyStartRow); } else if (start == 0) // We already have a first child mapping, but what we have mapped is not the first child anymore // so we need to update it. { updateFirstChildMapping(parent, end + 1); } } else { Q_ASSERT(proxyParent.isValid()); if (!isNewParent) { updateInternalIndexes(proxyParent, start, offset); } else { createParentMappings(parent.parent(), parent.row(), parent.row()); } } createParentMappings(parent, start, end); } void KSelectionProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int start, int end) { Q_Q(KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); if (!m_rowsInserted) { return; } m_rowsInserted = false; endInsertRows(parent, start, end); q->endInsertRows(); for (const PendingSelectionChange &pendingChange : qAsConst(m_pendingSelectionChanges)) { selectionChanged(pendingChange.selected, pendingChange.deselected); } m_pendingSelectionChanges.clear(); } static bool rootWillBeRemovedFrom(const QModelIndex &ancestor, int start, int end, const QModelIndex &root) { Q_ASSERT(root.isValid()); auto parent = root; while (parent.isValid()) { auto prev = parent; parent = parent.parent(); if (parent == ancestor) { return (prev.row() <= end && prev.row() >= start); } } return false; } bool KSelectionProxyModelPrivate::rootWillBeRemoved(const QItemSelection &selection, const QModelIndex &root) { Q_ASSERT(root.isValid()); for (auto& r : selection) { if (m_includeAllSelected) { if (r.parent() == root.parent() && root.row() <= r.bottom() && root.row() >= r.top()) { return true; } } else { if (rootWillBeRemovedFrom(r.parent(), r.top(), r.bottom(), root)) { return true; } } } return false; } QPair KSelectionProxyModelPrivate::beginRemoveRows(const QModelIndex &parent, int start, int end) const { Q_Q(const KSelectionProxyModel); if (!m_includeAllSelected && !m_omitChildren) { // SubTrees and SubTreesWithoutRoots const QModelIndex proxyParent = mapParentFromSource(parent); if (proxyParent.isValid()) { return qMakePair(start, end); } } if (m_startWithChildTrees && m_rootIndexList.contains(parent)) { const int proxyStartRow = getProxyInitialRow(parent) + start; const int proxyEndRow = proxyStartRow + (end - start); return qMakePair(proxyStartRow, proxyEndRow); } QList::const_iterator rootIt = m_rootIndexList.constBegin(); const QList::const_iterator rootEnd = m_rootIndexList.constEnd(); int proxyStartRemove = 0; for (; rootIt != rootEnd; ++rootIt) { if (rootWillBeRemovedFrom(parent, start, end, *rootIt)) { break; } else { if (m_startWithChildTrees) { proxyStartRemove += q->sourceModel()->rowCount(*rootIt); } else { ++proxyStartRemove; } } } if (rootIt == rootEnd) { return qMakePair(-1, -1); } int proxyEndRemove = proxyStartRemove; for (; rootIt != rootEnd; ++rootIt) { if (!rootWillBeRemovedFrom(parent, start, end, *rootIt)) { break; } if (m_startWithChildTrees) { proxyEndRemove += q->sourceModel()->rowCount(*rootIt); } else { ++proxyEndRemove; } } --proxyEndRemove; if (proxyEndRemove >= proxyStartRemove) { return qMakePair(proxyStartRemove, proxyEndRemove); } return qMakePair(-1, -1); } void KSelectionProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_Q(KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); if (!m_selectionModel || !m_selectionModel->hasSelection()) { return; } QPair pair = beginRemoveRows(parent, start, end); if (pair.first == -1) { return; } const QModelIndex proxyParent = mapParentFromSource(parent); m_rowsRemoved = true; m_proxyRemoveRows = pair; m_recreateFirstChildMappingOnRemoval = m_mappedFirstChildren.leftContains(q->sourceModel()->index(start, 0, parent)); q->beginRemoveRows(proxyParent, pair.first, pair.second); } void KSelectionProxyModelPrivate::endRemoveRows(const QModelIndex &sourceParent, int proxyStart, int proxyEnd) { const QModelIndex proxyParent = mapParentFromSource(sourceParent); // We need to make sure to remove entries from the mappings before updating internal indexes. // - A // - - B // - C // - - D // If A and C are selected, B and D are in the proxy. B maps to row 0 and D maps to row 1. // If B is then deleted leaving only D in the proxy, D needs to be updated to be a mapping // to row 0 instead of row 1. If that is done before removing the mapping for B, then the mapping // for D would overwrite the mapping for B and then the code for removing mappings would incorrectly // remove D. // So we first remove B and then update D. { SourceProxyIndexMapping::right_iterator it = m_mappedParents.rightBegin(); while (it != m_mappedParents.rightEnd()) { if (!it.value().isValid()) { m_parentIds.removeRight(it.key()); it = m_mappedParents.eraseRight(it); } else { ++it; } } } { // Depending on what is selected at the time, a single removal in the source could invalidate // many mapped first child items at once. // - A // - B // - - C // - - D // - - - E // - - - F // - - - - G // - - - - H // If D and F are selected, the proxy contains E, F, G, H. If B is then deleted E to H will // be removed, including both first child mappings at E and G. if (!proxyParent.isValid()) { removeFirstChildMappings(proxyStart, proxyEnd); } } if (proxyParent.isValid()) { updateInternalIndexes(proxyParent, proxyEnd + 1, -1 * (proxyEnd - proxyStart + 1)); } else { updateInternalTopIndexes(proxyEnd + 1, -1 * (proxyEnd - proxyStart + 1)); } QList::iterator rootIt = m_rootIndexList.begin(); while (rootIt != m_rootIndexList.end()) { if (!rootIt->isValid()) { rootIt = m_rootIndexList.erase(rootIt); } else { ++rootIt; } } } void KSelectionProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int start, int end) { Q_Q(KSelectionProxyModel); Q_UNUSED(end) Q_UNUSED(start) Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); if (!m_selectionModel) { return; } if (!m_rowsRemoved) { return; } m_rowsRemoved = false; Q_ASSERT(m_proxyRemoveRows.first >= 0); Q_ASSERT(m_proxyRemoveRows.second >= 0); endRemoveRows(parent, m_proxyRemoveRows.first, m_proxyRemoveRows.second); if (m_recreateFirstChildMappingOnRemoval && q->sourceModel()->hasChildren(parent)) // The private endRemoveRows call might remove the first child mapping for parent, so // we create it again in that case. { createFirstChildMapping(parent, m_proxyRemoveRows.first); } m_recreateFirstChildMappingOnRemoval = false; m_proxyRemoveRows = qMakePair(-1, -1); q->endRemoveRows(); } void KSelectionProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) { Q_UNUSED(srcParent) Q_UNUSED(srcStart) Q_UNUSED(srcEnd) Q_UNUSED(destParent) Q_UNUSED(destRow) // we cheat and just act like the layout changed; this might benefit from some // optimization sourceLayoutAboutToBeChanged(); } void KSelectionProxyModelPrivate::sourceRowsMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) { Q_UNUSED(srcParent) Q_UNUSED(srcStart) Q_UNUSED(srcEnd) Q_UNUSED(destParent) Q_UNUSED(destRow) // we cheat and just act like the layout changed; this might benefit from some // optimization sourceLayoutChanged(); } QModelIndex KSelectionProxyModelPrivate::mapParentToSource(const QModelIndex &proxyParent) const { return m_mappedParents.rightToLeft(proxyParent); } QModelIndex KSelectionProxyModelPrivate::mapParentFromSource(const QModelIndex &sourceParent) const { return m_mappedParents.leftToRight(sourceParent); } static bool indexIsValid(bool startWithChildTrees, int row, const QList &rootIndexList, const SourceIndexProxyRowMapping &mappedFirstChildren) { if (!startWithChildTrees) { Q_ASSERT(rootIndexList.size() > row); } else { Q_ASSERT(!mappedFirstChildren.isEmpty()); SourceIndexProxyRowMapping::right_const_iterator result = mappedFirstChildren.rightUpperBound(row) - 1; Q_ASSERT(result != mappedFirstChildren.rightEnd()); const int proxyFirstRow = result.key(); const QModelIndex sourceFirstChild = result.value(); Q_ASSERT(proxyFirstRow >= 0); Q_ASSERT(sourceFirstChild.isValid()); Q_ASSERT(sourceFirstChild.parent().isValid()); Q_ASSERT(row <= proxyFirstRow + sourceFirstChild.model()->rowCount(sourceFirstChild.parent())); } return true; } QModelIndex KSelectionProxyModelPrivate::createTopLevelIndex(int row, int column) const { Q_Q(const KSelectionProxyModel); Q_ASSERT(indexIsValid(m_startWithChildTrees, row, m_rootIndexList, m_mappedFirstChildren)); return q->createIndex(row, column); } QModelIndex KSelectionProxyModelPrivate::mapTopLevelFromSource(const QModelIndex &sourceIndex) const { Q_Q(const KSelectionProxyModel); const QModelIndex sourceParent = sourceIndex.parent(); const int row = m_rootIndexList.indexOf(sourceIndex); if (row == -1) { return QModelIndex(); } if (!m_startWithChildTrees) { Q_ASSERT(m_rootIndexList.size() > row); return q->createIndex(row, sourceIndex.column()); } if (!m_rootIndexList.contains(sourceParent)) { return QModelIndex(); } const QModelIndex firstChild = q->sourceModel()->index(0, 0, sourceParent); const int firstProxyRow = m_mappedFirstChildren.leftToRight(firstChild); return q->createIndex(firstProxyRow + sourceIndex.row(), sourceIndex.column()); } QModelIndex KSelectionProxyModelPrivate::mapFromSource(const QModelIndex &sourceIndex) const { Q_Q(const KSelectionProxyModel); const QModelIndex maybeMapped = mapParentFromSource(sourceIndex); if (maybeMapped.isValid()) { // Q_ASSERT((!d->m_startWithChildTrees && d->m_rootIndexList.contains(maybeMapped)) ? maybeMapped.row() < 0 : true ); return maybeMapped; } const QModelIndex sourceParent = sourceIndex.parent(); const QModelIndex proxyParent = mapParentFromSource(sourceParent); if (proxyParent.isValid()) { void *const parentId = m_parentIds.rightToLeft(proxyParent); static const int column = 0; return q->createIndex(sourceIndex.row(), column, parentId); } const QModelIndex firstChild = q->sourceModel()->index(0, 0, sourceParent); if (m_mappedFirstChildren.leftContains(firstChild)) { const int firstProxyRow = m_mappedFirstChildren.leftToRight(firstChild); return q->createIndex(firstProxyRow + sourceIndex.row(), sourceIndex.column()); } return mapTopLevelFromSource(sourceIndex); } int KSelectionProxyModelPrivate::topLevelRowCount() const { Q_Q(const KSelectionProxyModel); if (!m_startWithChildTrees) { return m_rootIndexList.size(); } if (m_mappedFirstChildren.isEmpty()) { return 0; } const SourceIndexProxyRowMapping::right_const_iterator result = m_mappedFirstChildren.rightConstEnd() - 1; const int proxyFirstRow = result.key(); const QModelIndex sourceFirstChild = result.value(); Q_ASSERT(sourceFirstChild.isValid()); const QModelIndex sourceParent = sourceFirstChild.parent(); Q_ASSERT(sourceParent.isValid()); return q->sourceModel()->rowCount(sourceParent) + proxyFirstRow; } bool KSelectionProxyModelPrivate::ensureMappable(const QModelIndex &parent) const { Q_Q(const KSelectionProxyModel); if (isFlat()) { return true; } if (parentIsMappable(parent)) { return true; } QModelIndex ancestor = parent.parent(); QModelIndexList ancestorList; while (ancestor.isValid()) { if (parentIsMappable(ancestor)) { break; } else { ancestorList.prepend(ancestor); } ancestor = ancestor.parent(); } if (!ancestor.isValid()) // @p parent is not a descendant of m_rootIndexList. { return false; } // sourceIndex can be mapped to the proxy. We just need to create mappings for its ancestors first. for (int i = 0; i < ancestorList.size(); ++i) { const QModelIndex existingAncestor = mapParentFromSource(ancestor); Q_ASSERT(existingAncestor.isValid()); void *const ansId = m_parentIds.rightToLeft(existingAncestor); const QModelIndex newSourceParent = ancestorList.at(i); const QModelIndex newProxyParent = q->createIndex(newSourceParent.row(), newSourceParent.column(), ansId); void *const newId = m_voidPointerFactory.createPointer(); m_parentIds.insert(newId, newProxyParent); m_mappedParents.insert(QPersistentModelIndex(newSourceParent), newProxyParent); ancestor = newSourceParent; } return true; } void KSelectionProxyModelPrivate::updateInternalTopIndexes(int start, int offset) { updateInternalIndexes(QModelIndex(), start, offset); QHash updates; { SourceIndexProxyRowMapping::right_iterator it = m_mappedFirstChildren.rightLowerBound(start); const SourceIndexProxyRowMapping::right_iterator end = m_mappedFirstChildren.rightEnd(); for (; it != end; ++it) { updates.insert(*it, it.key() + offset); } } { QHash::const_iterator it = updates.constBegin(); const QHash::const_iterator end = updates.constEnd(); for (; it != end; ++it) { m_mappedFirstChildren.insert(it.key(), it.value()); } } } void KSelectionProxyModelPrivate::updateInternalIndexes(const QModelIndex &parent, int start, int offset) { Q_Q(KSelectionProxyModel); Q_ASSERT(start + offset >= 0); Q_ASSERT(parent.isValid() ? parent.model() == q : true); if (isFlat()) { return; } SourceProxyIndexMapping::left_iterator mappedParentIt = m_mappedParents.leftBegin(); QHash updatedParentIds; QHash updatedParents; for (; mappedParentIt != m_mappedParents.leftEnd(); ++mappedParentIt) { const QModelIndex proxyIndex = mappedParentIt.value(); Q_ASSERT(proxyIndex.isValid()); if (proxyIndex.row() < start) { continue; } const QModelIndex proxyParent = proxyIndex.parent(); if (parent.isValid()) { if (proxyParent != parent) { continue; } } else { if (proxyParent.isValid()) { continue; } } Q_ASSERT(m_parentIds.rightContains(proxyIndex)); void *const key = m_parentIds.rightToLeft(proxyIndex); const QModelIndex newIndex = q->createIndex(proxyIndex.row() + offset, proxyIndex.column(), proxyIndex.internalPointer()); Q_ASSERT(newIndex.isValid()); updatedParentIds.insert(key, newIndex); updatedParents.insert(mappedParentIt.key(), newIndex); } { QHash::const_iterator it = updatedParents.constBegin(); const QHash::const_iterator end = updatedParents.constEnd(); for (; it != end; ++it) { m_mappedParents.insert(it.key(), it.value()); } } { QHash::const_iterator it = updatedParentIds.constBegin(); const QHash::const_iterator end = updatedParentIds.constEnd(); for (; it != end; ++it) { m_parentIds.insert(it.key(), it.value()); } } } bool KSelectionProxyModelPrivate::parentAlreadyMapped(const QModelIndex &parent) const { Q_Q(const KSelectionProxyModel); Q_UNUSED(q) // except in Q_ASSERT Q_ASSERT(parent.model() == q->sourceModel()); return m_mappedParents.leftContains(parent); } bool KSelectionProxyModelPrivate::firstChildAlreadyMapped(const QModelIndex &firstChild) const { Q_Q(const KSelectionProxyModel); Q_UNUSED(q) // except in Q_ASSERT Q_ASSERT(firstChild.model() == q->sourceModel()); return m_mappedFirstChildren.leftContains(firstChild); } void KSelectionProxyModelPrivate::createFirstChildMapping(const QModelIndex &parent, int proxyRow) const { Q_Q(const KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); static const int column = 0; static const int row = 0; const QPersistentModelIndex srcIndex = q->sourceModel()->index(row, column, parent); if (firstChildAlreadyMapped(srcIndex)) { return; } Q_ASSERT(srcIndex.isValid()); m_mappedFirstChildren.insert(srcIndex, proxyRow); } void KSelectionProxyModelPrivate::createParentMappings(const QModelIndex &parent, int start, int end) const { if (isFlat()) { return; } Q_Q(const KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q->sourceModel() : true); static const int column = 0; for (int row = start; row <= end; ++row) { const QModelIndex srcIndex = q->sourceModel()->index(row, column, parent); Q_ASSERT(srcIndex.isValid()); if (!q->sourceModel()->hasChildren(srcIndex) || parentAlreadyMapped(srcIndex)) { continue; } const QModelIndex proxyIndex = mapFromSource(srcIndex); if (!proxyIndex.isValid()) { return; // If one of them is not mapped, its siblings won't be either } void *const newId = m_voidPointerFactory.createPointer(); m_parentIds.insert(newId, proxyIndex); Q_ASSERT(srcIndex.isValid()); m_mappedParents.insert(QPersistentModelIndex(srcIndex), proxyIndex); } } void KSelectionProxyModelPrivate::removeFirstChildMappings(int start, int end) { SourceIndexProxyRowMapping::right_iterator it = m_mappedFirstChildren.rightLowerBound(start); const SourceIndexProxyRowMapping::right_iterator endIt = m_mappedFirstChildren.rightUpperBound(end); while (it != endIt) { it = m_mappedFirstChildren.eraseRight(it); } } void KSelectionProxyModelPrivate::removeParentMappings(const QModelIndex &parent, int start, int end) { Q_Q(KSelectionProxyModel); Q_ASSERT(parent.isValid() ? parent.model() == q : true); SourceProxyIndexMapping::right_iterator it = m_mappedParents.rightBegin(); SourceProxyIndexMapping::right_iterator endIt = m_mappedParents.rightEnd(); const bool flatList = isFlat(); while (it != endIt) { if (it.key().row() >= start && it.key().row() <= end) { const QModelIndex sourceParent = it.value(); const QModelIndex proxyGrandParent = mapParentFromSource(sourceParent.parent()); if (proxyGrandParent == parent) { if (!flatList) // Due to recursive calls, we could have several iterators on the container // when erase is called. That's safe according to the QHash::iterator docs though. { removeParentMappings(it.key(), 0, q->sourceModel()->rowCount(it.value()) - 1); } m_parentIds.removeRight(it.key()); it = m_mappedParents.eraseRight(it); } else { ++it; } } else { ++it; } } } QModelIndex KSelectionProxyModelPrivate::mapTopLevelToSource(int row, int column) const { if (!m_startWithChildTrees) { const QModelIndex idx = m_rootIndexList.at(row); return idx.sibling(idx.row(), column); } if (m_mappedFirstChildren.isEmpty()) { return QModelIndex(); } SourceIndexProxyRowMapping::right_iterator result = m_mappedFirstChildren.rightUpperBound(row) - 1; Q_ASSERT(result != m_mappedFirstChildren.rightEnd()); const int proxyFirstRow = result.key(); const QModelIndex sourceFirstChild = result.value(); Q_ASSERT(sourceFirstChild.isValid()); return sourceFirstChild.sibling(row - proxyFirstRow, column); } void KSelectionProxyModelPrivate::removeSelectionFromProxy(const QItemSelection &selection) { Q_Q(KSelectionProxyModel); if (selection.isEmpty()) { return; } QList::iterator rootIt = m_rootIndexList.begin(); const QList::iterator rootEnd = m_rootIndexList.end(); int proxyStartRemove = 0; for (; rootIt != rootEnd; ++rootIt) { if (rootWillBeRemoved(selection, *rootIt)) { break; } else { if (m_startWithChildTrees) { auto rc = q->sourceModel()->rowCount(*rootIt); proxyStartRemove += rc; } else { ++proxyStartRemove; } } } if (rootIt == rootEnd) { return; } int proxyEndRemove = proxyStartRemove; QList::iterator rootRemoveStart = rootIt; for (; rootIt != rootEnd; ++rootIt) { if (!rootWillBeRemoved(selection, *rootIt)) { break; } q->rootIndexAboutToBeRemoved(*rootIt); if (m_startWithChildTrees) { auto rc = q->sourceModel()->rowCount(*rootIt); proxyEndRemove += rc; } else { ++proxyEndRemove; } } --proxyEndRemove; if (proxyEndRemove >= proxyStartRemove) { q->beginRemoveRows(QModelIndex(), proxyStartRemove, proxyEndRemove); rootIt = m_rootIndexList.erase(rootRemoveStart, rootIt); removeParentMappings(QModelIndex(), proxyStartRemove, proxyEndRemove); if (m_startWithChildTrees) { removeFirstChildMappings(proxyStartRemove, proxyEndRemove); } updateInternalTopIndexes(proxyEndRemove + 1, -1 * (proxyEndRemove - proxyStartRemove + 1)); q->endRemoveRows(); } else { rootIt = m_rootIndexList.erase(rootRemoveStart, rootIt); } if (rootIt != rootEnd) { removeSelectionFromProxy(selection); } } void KSelectionProxyModelPrivate::selectionChanged(const QItemSelection &_selected, const QItemSelection &_deselected) { Q_Q(KSelectionProxyModel); if (!q->sourceModel() || (_selected.isEmpty() && _deselected.isEmpty())) { return; } if (m_sourceModelResetting) { return; } if (m_rowsInserted || m_rowsRemoved) { m_pendingSelectionChanges.append(PendingSelectionChange(_selected, _deselected)); return; } // Any deselected indexes in the m_rootIndexList are removed. Then, any // indexes in the selected range which are not a descendant of one of the already selected indexes // are inserted into the model. // // All ranges from the selection model need to be split into individual rows. Ranges which are contiguous in // the selection model may not be contiguous in the source model if there's a sort filter proxy model in the chain. // // Some descendants of deselected indexes may still be selected. The ranges in m_selectionModel->selection() // are examined. If any of the ranges are descendants of one of the // indexes in deselected, they are added to the ranges to be inserted into the model. // // The new indexes are inserted in sorted order. const QItemSelection selected = kNormalizeSelection(m_indexMapper->mapSelectionRightToLeft(_selected)); const QItemSelection deselected = kNormalizeSelection(m_indexMapper->mapSelectionRightToLeft(_deselected)); #if QT_VERSION < 0x040800 // The QItemSelectionModel sometimes doesn't remove deselected items from its selection // Fixed in Qt 4.8 : http://qt.gitorious.org/qt/qt/merge_requests/2403 QItemSelection reportedSelection = m_selectionModel->selection(); reportedSelection.merge(deselected, QItemSelectionModel::Deselect); QItemSelection fullSelection = m_indexMapper->mapSelectionRightToLeft(reportedSelection); #else QItemSelection fullSelection = m_indexMapper->mapSelectionRightToLeft(m_selectionModel->selection()); #endif fullSelection = kNormalizeSelection(fullSelection); QItemSelection newRootRanges; QItemSelection removedRootRanges; if (!m_includeAllSelected) { newRootRanges = getRootRanges(selected); QItemSelection existingSelection = fullSelection; // What was selected before the selection was made. existingSelection.merge(selected, QItemSelectionModel::Deselect); // This is similar to m_rootRanges, but that m_rootRanges at this point still contains the roots // of deselected and existingRootRanges does not. const QItemSelection existingRootRanges = getRootRanges(existingSelection); { QMutableListIterator i(newRootRanges); while (i.hasNext()) { const QItemSelectionRange range = i.next(); const QModelIndex topLeft = range.topLeft(); if (isDescendantOf(existingRootRanges, topLeft)) { i.remove(); } } } QItemSelection exposedSelection; { QItemSelection deselectedRootRanges = getRootRanges(deselected); QListIterator i(deselectedRootRanges); while (i.hasNext()) { const QItemSelectionRange range = i.next(); const QModelIndex topLeft = range.topLeft(); // Consider this: // // - A // - - B // - - - C // - - - - D // // B and D were selected, then B was deselected and C was selected in one go. if (!isDescendantOf(existingRootRanges, topLeft)) { // B is topLeft and fullSelection contains D. // B is not a descendant of D. // range is not a descendant of the selection, but maybe the selection is a descendant of range. // no need to check selected here. That's already in newRootRanges. // existingRootRanges and newRootRanges do not overlap. for (const QItemSelectionRange &selectedRange : existingRootRanges) { const QModelIndex selectedRangeTopLeft = selectedRange.topLeft(); // existingSelection (and selectedRangeTopLeft) is D. // D is a descendant of B, so when B was removed, D might have been exposed as a root. if (isDescendantOf(range, selectedRangeTopLeft) // But D is also a descendant of part of the new selection C, which is already set to be a new root // so D would not be added to exposedSelection because C is in newRootRanges. && !isDescendantOf(newRootRanges, selectedRangeTopLeft)) { exposedSelection.append(selectedRange); } } removedRootRanges << range; } } } QItemSelection obscuredRanges; { QListIterator i(existingRootRanges); while (i.hasNext()) { const QItemSelectionRange range = i.next(); if (isDescendantOf(newRootRanges, range.topLeft())) { obscuredRanges << range; } } } removedRootRanges << getRootRanges(obscuredRanges); newRootRanges << getRootRanges(exposedSelection); removedRootRanges = kNormalizeSelection(removedRootRanges); newRootRanges = kNormalizeSelection(newRootRanges); } else { removedRootRanges = deselected; newRootRanges = selected; } removeSelectionFromProxy(removedRootRanges); if (!m_selectionModel->hasSelection()) { Q_ASSERT(m_rootIndexList.isEmpty()); Q_ASSERT(m_mappedFirstChildren.isEmpty()); Q_ASSERT(m_mappedParents.isEmpty()); Q_ASSERT(m_parentIds.isEmpty()); } insertSelectionIntoProxy(newRootRanges); } int KSelectionProxyModelPrivate::getTargetRow(int rootListRow) { Q_Q(KSelectionProxyModel); if (!m_startWithChildTrees) { return rootListRow; } --rootListRow; while (rootListRow >= 0) { const QModelIndex idx = m_rootIndexList.at(rootListRow); Q_ASSERT(idx.isValid()); const int rowCount = q->sourceModel()->rowCount(idx); if (rowCount > 0) { static const int column = 0; const QModelIndex srcIdx = q->sourceModel()->index(rowCount - 1, column, idx); const QModelIndex proxyLastChild = mapFromSource(srcIdx); return proxyLastChild.row() + 1; } --rootListRow; } return 0; } void KSelectionProxyModelPrivate::insertSelectionIntoProxy(const QItemSelection &selection) { Q_Q(KSelectionProxyModel); if (selection.isEmpty()) { return; } const auto lst = selection.indexes(); for (const QModelIndex &newIndex : lst) { Q_ASSERT(newIndex.model() == q->sourceModel()); if (newIndex.column() > 0) { continue; } if (m_startWithChildTrees) { const int rootListRow = getRootListRow(m_rootIndexList, newIndex); Q_ASSERT(q->sourceModel() == newIndex.model()); const int rowCount = q->sourceModel()->rowCount(newIndex); const int startRow = getTargetRow(rootListRow); if (rowCount == 0) { // Even if the newindex doesn't have any children to put into the model yet, // We still need to make sure its future children are inserted into the model. m_rootIndexList.insert(rootListRow, newIndex); if (!m_resetting || m_layoutChanging) { - emit q->rootIndexAdded(newIndex); + Q_EMIT q->rootIndexAdded(newIndex); } continue; } if (!m_resetting) { q->beginInsertRows(QModelIndex(), startRow, startRow + rowCount - 1); } Q_ASSERT(newIndex.isValid()); m_rootIndexList.insert(rootListRow, newIndex); if (!m_resetting || m_layoutChanging) { - emit q->rootIndexAdded(newIndex); + Q_EMIT q->rootIndexAdded(newIndex); } int _start = 0; for (int i = 0; i < rootListRow; ++i) { _start += q->sourceModel()->rowCount(m_rootIndexList.at(i)); } updateInternalTopIndexes(_start, rowCount); createFirstChildMapping(newIndex, _start); createParentMappings(newIndex, 0, rowCount - 1); if (!m_resetting) { q->endInsertRows(); } } else { const int row = getRootListRow(m_rootIndexList, newIndex); if (!m_resetting) { q->beginInsertRows(QModelIndex(), row, row); } Q_ASSERT(newIndex.isValid()); m_rootIndexList.insert(row, newIndex); if (!m_resetting || m_layoutChanging) { - emit q->rootIndexAdded(newIndex); + Q_EMIT q->rootIndexAdded(newIndex); } Q_ASSERT(m_rootIndexList.size() > row); updateInternalIndexes(QModelIndex(), row, 1); createParentMappings(newIndex.parent(), newIndex.row(), newIndex.row()); if (!m_resetting) { q->endInsertRows(); } } } q->rootSelectionAdded(selection); } KSelectionProxyModel::KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent) : QAbstractProxyModel(parent), d_ptr(new KSelectionProxyModelPrivate(this)) { setSelectionModel(selectionModel); } KSelectionProxyModel::KSelectionProxyModel() : QAbstractProxyModel(nullptr), d_ptr(new KSelectionProxyModelPrivate(this)) { } KSelectionProxyModel::~KSelectionProxyModel() { delete d_ptr; } void KSelectionProxyModel::setFilterBehavior(FilterBehavior behavior) { Q_D(KSelectionProxyModel); Q_ASSERT(behavior != InvalidBehavior); if (behavior == InvalidBehavior) { return; } if (d->m_filterBehavior != behavior) { beginResetModel(); d->m_filterBehavior = behavior; switch (behavior) { case InvalidBehavior: { Q_ASSERT(!"InvalidBehavior can't be used here"); return; } case SubTrees: { d->m_omitChildren = false; d->m_omitDescendants = false; d->m_startWithChildTrees = false; d->m_includeAllSelected = false; break; } case SubTreeRoots: { d->m_omitChildren = true; d->m_startWithChildTrees = false; d->m_includeAllSelected = false; break; } case SubTreesWithoutRoots: { d->m_omitChildren = false; d->m_omitDescendants = false; d->m_startWithChildTrees = true; d->m_includeAllSelected = false; break; } case ExactSelection: { d->m_omitChildren = true; d->m_startWithChildTrees = false; d->m_includeAllSelected = true; break; } case ChildrenOfExactSelection: { d->m_omitChildren = false; d->m_omitDescendants = true; d->m_startWithChildTrees = true; d->m_includeAllSelected = true; break; } } - emit filterBehaviorChanged(); + Q_EMIT filterBehaviorChanged(); d->resetInternalData(); if (d->m_selectionModel) { d->selectionChanged(d->m_selectionModel->selection(), QItemSelection()); } endResetModel(); } } KSelectionProxyModel::FilterBehavior KSelectionProxyModel::filterBehavior() const { Q_D(const KSelectionProxyModel); return d->m_filterBehavior; } void KSelectionProxyModel::setSourceModel(QAbstractItemModel *_sourceModel) { Q_D(KSelectionProxyModel); Q_ASSERT(_sourceModel != this); if (_sourceModel == sourceModel()) { return; } beginResetModel(); d->m_resetting = true; 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]); } } // Must be called before QAbstractProxyModel::setSourceModel because it emit some signals. d->resetInternalData(); QAbstractProxyModel::setSourceModel(_sourceModel); if (_sourceModel) { if (d->m_selectionModel) { delete d->m_indexMapper; d->m_indexMapper = new KModelIndexProxyMapper(_sourceModel, d->m_selectionModel->model(), this); if (d->m_selectionModel->hasSelection()) { d->selectionChanged(d->m_selectionModel->selection(), QItemSelection()); } } for (int i = 0; i < int(sizeof modelSignals / sizeof * modelSignals); ++i) { connect(_sourceModel, modelSignals[i], this, proxySlots[i]); } } d->m_resetting = false; endResetModel(); } QModelIndex KSelectionProxyModel::mapToSource(const QModelIndex &proxyIndex) const { Q_D(const KSelectionProxyModel); if (!proxyIndex.isValid() || !sourceModel() || d->m_rootIndexList.isEmpty()) { return QModelIndex(); } Q_ASSERT(proxyIndex.model() == this); if (proxyIndex.internalPointer() == nullptr) { return d->mapTopLevelToSource(proxyIndex.row(), proxyIndex.column()); } const QModelIndex proxyParent = d->parentForId(proxyIndex.internalPointer()); Q_ASSERT(proxyParent.isValid()); const QModelIndex sourceParent = d->mapParentToSource(proxyParent); Q_ASSERT(sourceParent.isValid()); return sourceModel()->index(proxyIndex.row(), proxyIndex.column(), sourceParent); } QModelIndex KSelectionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { Q_D(const KSelectionProxyModel); if (!sourceModel() || !sourceIndex.isValid() || d->m_rootIndexList.isEmpty()) { return QModelIndex(); } Q_ASSERT(sourceIndex.model() == sourceModel()); if (!sourceIndex.isValid()) { return QModelIndex(); } if (!d->ensureMappable(sourceIndex)) { return QModelIndex(); } return d->mapFromSource(sourceIndex); } int KSelectionProxyModel::rowCount(const QModelIndex &index) const { Q_D(const KSelectionProxyModel); if (!sourceModel() || index.column() > 0 || d->m_rootIndexList.isEmpty()) { return 0; } Q_ASSERT(index.isValid() ? index.model() == this : true); if (!index.isValid()) { return d->topLevelRowCount(); } // index is valid if (d->isFlat()) { return 0; } QModelIndex sourceParent = d->mapParentToSource(index); if (!sourceParent.isValid() && sourceModel()->hasChildren(sourceParent)) { sourceParent = mapToSource(index.parent()); d->createParentMappings(sourceParent, 0, sourceModel()->rowCount(sourceParent) - 1); sourceParent = d->mapParentToSource(index); } if (!sourceParent.isValid()) { return 0; } return sourceModel()->rowCount(sourceParent); } QModelIndex KSelectionProxyModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const KSelectionProxyModel); if (!sourceModel() || d->m_rootIndexList.isEmpty() || !hasIndex(row, column, parent)) { return QModelIndex(); } Q_ASSERT(parent.isValid() ? parent.model() == this : true); if (!parent.isValid()) { return d->createTopLevelIndex(row, column); } void *const parentId = d->parentId(parent); Q_ASSERT(parentId); return createIndex(row, column, parentId); } QModelIndex KSelectionProxyModel::parent(const QModelIndex &index) const { Q_D(const KSelectionProxyModel); if (!sourceModel() || !index.isValid() || d->m_rootIndexList.isEmpty()) { return QModelIndex(); } Q_ASSERT(index.model() == this); return d->parentForId(index.internalPointer()); } Qt::ItemFlags KSelectionProxyModel::flags(const QModelIndex &index) const { if (!index.isValid() || !sourceModel()) { return QAbstractProxyModel::flags(index); } Q_ASSERT(index.model() == this); const QModelIndex srcIndex = mapToSource(index); Q_ASSERT(srcIndex.isValid()); return sourceModel()->flags(srcIndex); } QVariant KSelectionProxyModel::data(const QModelIndex &index, int role) const { if (!sourceModel()) { return QVariant(); } if (index.isValid()) { Q_ASSERT(index.model() == this); const QModelIndex idx = mapToSource(index); return idx.data(role); } return sourceModel()->data(index, role); } QVariant KSelectionProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (!sourceModel()) { return QVariant(); } return sourceModel()->headerData(section, orientation, role); } QMimeData *KSelectionProxyModel::mimeData(const QModelIndexList &indexes) const { if (!sourceModel()) { return QAbstractProxyModel::mimeData(indexes); } QModelIndexList sourceIndexes; for (const QModelIndex &index : indexes) { sourceIndexes << mapToSource(index); } return sourceModel()->mimeData(sourceIndexes); } QStringList KSelectionProxyModel::mimeTypes() const { if (!sourceModel()) { return QAbstractProxyModel::mimeTypes(); } return sourceModel()->mimeTypes(); } Qt::DropActions KSelectionProxyModel::supportedDropActions() const { if (!sourceModel()) { return QAbstractProxyModel::supportedDropActions(); } return sourceModel()->supportedDropActions(); } bool KSelectionProxyModel::hasChildren(const QModelIndex &parent) const { Q_D(const KSelectionProxyModel); if (d->m_rootIndexList.isEmpty() || !sourceModel()) { return false; } if (parent.isValid()) { Q_ASSERT(parent.model() == this); if (d->isFlat()) { return false; } return sourceModel()->hasChildren(mapToSource(parent)); } if (!d->m_startWithChildTrees) { return true; } return !d->m_mappedFirstChildren.isEmpty(); } int KSelectionProxyModel::columnCount(const QModelIndex &index) const { if (!sourceModel() || index.column() > 0) { return 0; } return sourceModel()->columnCount(mapToSource(index)); } QItemSelectionModel *KSelectionProxyModel::selectionModel() const { Q_D(const KSelectionProxyModel); return d->m_selectionModel; } void KSelectionProxyModel::setSelectionModel(QItemSelectionModel *itemSelectionModel) { Q_D(KSelectionProxyModel); if (d->m_selectionModel != itemSelectionModel) { if (d->m_selectionModel) { disconnect(d->m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection))); } d->m_selectionModel = itemSelectionModel; - emit selectionModelChanged(); + Q_EMIT selectionModelChanged(); if (d->m_selectionModel) { connect(d->m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged(QItemSelection,QItemSelection))); auto handleSelectionModelModel = [ &, d] { beginResetModel(); if (d->selectionModelModelAboutToBeResetConnection) { disconnect(d->selectionModelModelAboutToBeResetConnection); } if (d->selectionModelModelResetConnection) { disconnect(d->selectionModelModelResetConnection); } if (d->m_selectionModel->model()) { d->selectionModelModelAboutToBeResetConnection = connect( d->m_selectionModel->model(), SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset())); d->selectionModelModelResetConnection = connect( d->m_selectionModel->model(), SIGNAL(modelReset()), this, SLOT(sourceModelReset())); d->m_rootIndexList.clear(); delete d->m_indexMapper; d->m_indexMapper = new KModelIndexProxyMapper(sourceModel(), d->m_selectionModel->model(), this); } endResetModel(); }; connect(d->m_selectionModel.data(), &QItemSelectionModel::modelChanged, this, handleSelectionModelModel); handleSelectionModelModel(); } if (sourceModel()) { delete d->m_indexMapper; d->m_indexMapper = new KModelIndexProxyMapper(sourceModel(), d->m_selectionModel->model(), this); if (d->m_selectionModel->hasSelection()) { d->selectionChanged(d->m_selectionModel->selection(), QItemSelection()); } } } } bool KSelectionProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_D(const KSelectionProxyModel); if (!sourceModel() || d->m_rootIndexList.isEmpty()) { return false; } if ((row == -1) && (column == -1)) { return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); } int source_destination_row = -1; int source_destination_column = -1; QModelIndex source_parent; if (row == rowCount(parent)) { source_parent = mapToSource(parent); source_destination_row = sourceModel()->rowCount(source_parent); } else { const QModelIndex proxy_index = index(row, column, parent); const QModelIndex source_index = mapToSource(proxy_index); source_destination_row = source_index.row(); source_destination_column = source_index.column(); source_parent = source_index.parent(); } return sourceModel()->dropMimeData(data, action, source_destination_row, source_destination_column, source_parent); } QList KSelectionProxyModel::sourceRootIndexes() const { Q_D(const KSelectionProxyModel); return d->m_rootIndexList; } QModelIndexList KSelectionProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role < Qt::UserRole) { return QAbstractProxyModel::match(start, role, value, hits, flags); } QModelIndexList list; QModelIndex proxyIndex; const auto lst = sourceModel()->match(mapToSource(start), role, value, hits, flags); for (const QModelIndex &idx : lst) { proxyIndex = mapFromSource(idx); if (proxyIndex.isValid()) { list << proxyIndex; } } return list; } QItemSelection KSelectionProxyModel::mapSelectionFromSource(const QItemSelection &selection) const { Q_D(const KSelectionProxyModel); if (!d->m_startWithChildTrees && d->m_includeAllSelected) { // QAbstractProxyModel::mapSelectionFromSource puts invalid ranges in the result // without checking. We can't have that. QItemSelection proxySelection; for (const QItemSelectionRange &range : selection) { QModelIndex proxyTopLeft = mapFromSource(range.topLeft()); if (!proxyTopLeft.isValid()) { continue; } QModelIndex proxyBottomRight = mapFromSource(range.bottomRight()); Q_ASSERT(proxyBottomRight.isValid()); proxySelection.append(QItemSelectionRange(proxyTopLeft, proxyBottomRight)); } return proxySelection; } QItemSelection proxySelection; QItemSelection::const_iterator it = selection.constBegin(); const QItemSelection::const_iterator end = selection.constEnd(); for (; it != end; ++it) { const QModelIndex proxyTopLeft = mapFromSource(it->topLeft()); if (!proxyTopLeft.isValid()) { continue; } if (it->height() == 1 && it->width() == 1) { proxySelection.append(QItemSelectionRange(proxyTopLeft, proxyTopLeft)); } else { proxySelection.append(QItemSelectionRange(proxyTopLeft, d->mapFromSource(it->bottomRight()))); } } return proxySelection; } QItemSelection KSelectionProxyModel::mapSelectionToSource(const QItemSelection &selection) const { Q_D(const KSelectionProxyModel); if (selection.isEmpty()) { return selection; } if (!d->m_startWithChildTrees && d->m_includeAllSelected) { // QAbstractProxyModel::mapSelectionFromSource puts invalid ranges in the result // without checking. We can't have that. QItemSelection sourceSelection; for (const QItemSelectionRange &range : selection) { QModelIndex sourceTopLeft = mapToSource(range.topLeft()); Q_ASSERT(sourceTopLeft.isValid()); QModelIndex sourceBottomRight = mapToSource(range.bottomRight()); Q_ASSERT(sourceBottomRight.isValid()); sourceSelection.append(QItemSelectionRange(sourceTopLeft, sourceBottomRight)); } return sourceSelection; } QItemSelection sourceSelection; QItemSelection extraSelection; QItemSelection::const_iterator it = selection.constBegin(); const QItemSelection::const_iterator end = selection.constEnd(); for (; it != end; ++it) { const QModelIndex sourceTopLeft = mapToSource(it->topLeft()); if (it->height() == 1 && it->width() == 1) { sourceSelection.append(QItemSelectionRange(sourceTopLeft, sourceTopLeft)); } else if (it->parent().isValid()) { sourceSelection.append(QItemSelectionRange(sourceTopLeft, mapToSource(it->bottomRight()))); } else { // A contiguous selection in the proxy might not be contiguous in the source if it // is at the top level of the proxy. if (d->m_startWithChildTrees) { const QModelIndex sourceParent = mapFromSource(sourceTopLeft); Q_ASSERT(sourceParent.isValid()); const int rowCount = sourceModel()->rowCount(sourceParent); if (rowCount < it->bottom()) { Q_ASSERT(sourceTopLeft.isValid()); Q_ASSERT(it->bottomRight().isValid()); const QModelIndex sourceBottomRight = mapToSource(it->bottomRight()); Q_ASSERT(sourceBottomRight.isValid()); sourceSelection.append(QItemSelectionRange(sourceTopLeft, sourceBottomRight)); continue; } // Store the contiguous part... const QModelIndex sourceBottomRight = sourceModel()->index(rowCount - 1, it->right(), sourceParent); Q_ASSERT(sourceTopLeft.isValid()); Q_ASSERT(sourceBottomRight.isValid()); sourceSelection.append(QItemSelectionRange(sourceTopLeft, sourceBottomRight)); // ... and the rest will be processed later. extraSelection.append(QItemSelectionRange(createIndex(it->top() - rowCount, it->right()), it->bottomRight())); } else { QItemSelection topSelection; const QModelIndex idx = createIndex(it->top(), it->right()); const QModelIndex sourceIdx = mapToSource(idx); Q_ASSERT(sourceIdx.isValid()); topSelection.append(QItemSelectionRange(sourceTopLeft, sourceIdx)); for (int i = it->top() + 1; i <= it->bottom(); ++i) { const QModelIndex left = mapToSource(createIndex(i, 0)); const QModelIndex right = mapToSource(createIndex(i, it->right())); Q_ASSERT(left.isValid()); Q_ASSERT(right.isValid()); topSelection.append(QItemSelectionRange(left, right)); } sourceSelection += kNormalizeSelection(topSelection); } } } sourceSelection << mapSelectionToSource(extraSelection); return sourceSelection; } #include "moc_kselectionproxymodel.cpp" diff --git a/tests/proxymodeltestapp/kreparentingproxymodel.cpp b/tests/proxymodeltestapp/kreparentingproxymodel.cpp index 234b55a..84b85b2 100644 --- a/tests/proxymodeltestapp/kreparentingproxymodel.cpp +++ b/tests/proxymodeltestapp/kreparentingproxymodel.cpp @@ -1,1515 +1,1515 @@ /* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kreparentingproxymodel.h" #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. */ QVector 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 QVector 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); } QVector KReparentingProxyModelPrivate::getExistingAncestors(const QModelIndex &descendant) const { Q_Q(const KReparentingProxyModel); QVector 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; } QVector 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 QVector::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 : qAsConst(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 : qAsConst(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); // -// emit q->rowsAboutToBeRemoved(proxyParent, start, i); +// 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; QMutableVectorIterator it(m_pendingRemovals); while (it.hasNext()) { PendingRemoval removal = it.next(); m_pendingRemovalChildIndexes.remove(removal.sourceIndex); m_pendingRemovalParents.remove(parent.internalId()); it.remove(); - emit q->endRemoveRows(); + 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 - emit q->layoutAboutToBeChanged(); + 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(); - emit q->layoutChanged(); + 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); } - emit q->dataChanged(startIndex, lastAffectedSibling); + 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" diff --git a/tests/proxymodeltestapp/lessthanwidget.cpp b/tests/proxymodeltestapp/lessthanwidget.cpp index 3349dea..890918c 100644 --- a/tests/proxymodeltestapp/lessthanwidget.cpp +++ b/tests/proxymodeltestapp/lessthanwidget.cpp @@ -1,103 +1,103 @@ /* This file is part of the proxy model test suite. SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.1-or-later */ #include "lessthanwidget.h" #include #include #include ColoredTreeModel::ColoredTreeModel(QObject *parent) : DynamicTreeModel(parent), m_selectionModel(nullptr), m_lessThanColour(Qt::yellow), m_greaterThanColour(Qt::red) { } void ColoredTreeModel::setSelectionModel(QItemSelectionModel *selectionModel) { m_selectionModel = selectionModel; connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(recolor())); } void ColoredTreeModel::recolor(const QModelIndex &parent) { const QModelIndex topLeft = index(0, 0, parent); const int _rowCount = rowCount(parent); const QModelIndex bottomRight = index(_rowCount - 1, columnCount(parent) - 1, parent); - emit dataChanged(topLeft, bottomRight); + Q_EMIT dataChanged(topLeft, bottomRight); static const int column = 0; QModelIndex idx; for (int row = 0; row < _rowCount; ++row) { idx = index(row, column, parent); if (hasChildren(idx)) { recolor(idx); } } } QVariant ColoredTreeModel::data(const QModelIndex &index, int role) const { if (role != Qt::BackgroundRole || !m_selectionModel || m_selectionModel->selection().indexes().size() != 1) { return DynamicTreeModel::data(index, role); } const QModelIndex selectedIndex = m_selectionModel->selection().indexes().first(); if (index == selectedIndex) { return DynamicTreeModel::data(index, role); } if (index < selectedIndex) { return m_lessThanColour; } Q_ASSERT(selectedIndex < index); return m_greaterThanColour; } void LessThanWidget::insertGrid(QList address) { ModelInsertCommand *ins = new ModelInsertCommand(m_coloredTreeModel, this); ins->setAncestorRowNumbers(address); ins->setNumCols(5); ins->setStartRow(0); ins->setEndRow(5); ins->doCommand(); } LessThanWidget::LessThanWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { QLabel *explanation = new QLabel(this); explanation->setText(QLatin1String("The yellow items are 'less than' the selected item according to QModelIndex::operator<().\n" "The red items are greater than the selected item (i.e, not less than and not equal).")); m_coloredTreeModel = new ColoredTreeModel(this); QTreeView *treeView = new QTreeView(this); treeView->setModel(m_coloredTreeModel); treeView->setSelectionBehavior(QAbstractItemView::SelectItems); treeView->setSelectionMode(QTreeView::SingleSelection); m_coloredTreeModel->setSelectionModel(treeView->selectionModel()); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(explanation); layout->addWidget(treeView); insertGrid(QList()); insertGrid(QList() << 2); insertGrid(QList() << 3); insertGrid(QList() << 4); insertGrid(QList() << 3 << 2); insertGrid(QList() << 3 << 3); insertGrid(QList() << 3 << 4); }