// Copyright (C) 2009 Stephen Kelly // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dynamictreemodel.h" #include #include #include #include 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); if (parent.column() >= parentTable.size()) qFatal("%s: parent.column() must be less than parentTable.size()", Q_FUNC_INFO); QList parentSiblings = parentTable.at(parent.column()); if (parent.row() >= parentSiblings.size()) qFatal("%s: parent.row() must be less than parentSiblings.size()", Q_FUNC_INFO); } 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; for (auto i = m_childItems.cbegin(), end = m_childItems.cend(); i != end; ++i) { for (const auto &list : i.value()) { if (list.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 (Qt::DisplayRole == role) return m_items.value(index.internalId()); return QVariant(); } void DynamicTreeModel::clear() { beginResetModel(); m_items.clear(); m_childItems.clear(); nextId = 1; endResetModel(); } ModelChangeCommand::ModelChangeCommand(DynamicTreeModel *model, QObject *parent) : QObject(parent), m_model(model), m_numCols(1), m_startRow(-1), m_endRow(-1) { } QModelIndex ModelChangeCommand::findIndex(const QList &rows) const { const int col = 0; QModelIndex parent = QModelIndex(); for (int row : rows) { parent = m_model->index(row, col, parent); if (!parent.isValid()) qFatal("%s: parent must be valid", Q_FUNC_INFO); } return parent; } ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } void ModelInsertCommand::doCommand() { QModelIndex parent = findIndex(m_rowNumbers); 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()); // QString name = QUuid::createUuid().toString(); 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(); } 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; } for (qint64 id : l) m_model->m_childItems[destParent.internalId()][column].insert(d++, id); } emitPostSignal(); } void ModelMoveCommand::emitPostSignal() { m_model->endMoveRows(); } ModelResetCommand::ModelResetCommand(DynamicTreeModel *model, QObject *parent) : ModelMoveCommand(model, parent) { } ModelResetCommand::~ModelResetCommand() { } bool ModelResetCommand::emitPreSignal(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); return true; } void ModelResetCommand::emitPostSignal() { m_model->beginResetModel(); m_model->endResetModel(); } ModelResetCommandFixed::ModelResetCommandFixed(DynamicTreeModel *model, QObject *parent) : ModelMoveCommand(model, parent) { } ModelResetCommandFixed::~ModelResetCommandFixed() { } bool ModelResetCommandFixed::emitPreSignal(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); m_model->beginResetModel(); return true; } void ModelResetCommandFixed::emitPostSignal() { m_model->endResetModel(); } ModelChangeChildrenLayoutsCommand::ModelChangeChildrenLayoutsCommand(DynamicTreeModel *model, QObject *parent) : ModelChangeCommand(model, parent) { } void ModelChangeChildrenLayoutsCommand::doCommand() { const QPersistentModelIndex parent1 = findIndex(m_rowNumbers); const QPersistentModelIndex parent2 = findIndex(m_secondRowNumbers); const QList parents = { parent1, parent2 }; emit m_model->layoutAboutToBeChanged(parents); int rowSize1 = -1; int rowSize2 = -1; for (int column = 0; column < m_numCols; ++column) { { QList &l = m_model->m_childItems[parent1.internalId()][column]; rowSize1 = l.size(); l.prepend(l.takeLast()); } { QList &l = m_model->m_childItems[parent2.internalId()][column]; rowSize2 = l.size(); l.append(l.takeFirst()); } } // If we're changing one of the parent indexes, we need to ensure that we do that before // changing any children of that parent. The reason is that we're keeping parent1 and parent2 // around as QPersistentModelIndex instances, and we query idx.parent() in the loop. QModelIndexList persistent = m_model->persistentIndexList(); for (const QModelIndex &parent : parents) { int idx = persistent.indexOf(parent); if (idx != -1) persistent.move(idx, 0); } for (const QModelIndex &idx : std::as_const(persistent)) { if (idx.parent() == parent1) { if (idx.row() == rowSize1 - 1) { m_model->changePersistentIndex(idx, m_model->createIndex(0, idx.column(), idx.internalPointer())); } else { m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() + 1, idx.column(), idx.internalPointer())); } } else if (idx.parent() == parent2) { if (idx.row() == 0) { m_model->changePersistentIndex(idx, m_model->createIndex(rowSize2 - 1, idx.column(), idx.internalPointer())); } else { m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() - 1, idx.column(), idx.internalPointer())); } } } emit m_model->layoutChanged(parents); }