File indexing completed on 2024-04-14 04:45:52

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "abstracttreemodel.hpp"
0007 
0008 #include "treeitem.hpp"
0009 #include <QDebug>
0010 #include <utility>
0011 
0012 #include <queue>
0013 #include <unordered_set>
0014 
0015 int AbstractTreeModel::currentTreeId = 0;
0016 AbstractTreeModel::AbstractTreeModel(QObject *parent)
0017     : QAbstractItemModel(parent)
0018 {
0019 }
0020 
0021 std::shared_ptr<AbstractTreeModel> AbstractTreeModel::construct(QObject *parent)
0022 {
0023     std::shared_ptr<AbstractTreeModel> self(new AbstractTreeModel(parent));
0024     self->rootItem = TreeItem::construct(QList<QVariant>(), self, true);
0025     return self;
0026 }
0027 
0028 AbstractTreeModel::~AbstractTreeModel()
0029 {
0030     m_allItems.clear();
0031     rootItem.reset();
0032 }
0033 
0034 int AbstractTreeModel::columnCount(const QModelIndex &parent) const
0035 {
0036     if (!parent.isValid()) return rootItem->columnCount();
0037 
0038     const auto id = int(parent.internalId());
0039     auto item = getItemById(id);
0040     return item->columnCount();
0041 }
0042 
0043 QVariant AbstractTreeModel::data(const QModelIndex &index, int role) const
0044 {
0045     if (!index.isValid()) {
0046         return QVariant();
0047     }
0048 
0049     if (role != Qt::DisplayRole) {
0050         return QVariant();
0051     }
0052     auto item = getItemById(int(index.internalId()));
0053     return item->dataColumn(index.column());
0054 }
0055 
0056 Qt::ItemFlags AbstractTreeModel::flags(const QModelIndex &index) const
0057 {
0058     const auto flags = QAbstractItemModel::flags(index);
0059     if (index.isValid()) {
0060         auto item = getItemById(int(index.internalId()));
0061         if (item->depth() == 1) {
0062             return flags & ~Qt::ItemIsSelectable;
0063         }
0064     }
0065     return flags;
0066 }
0067 
0068 QVariant AbstractTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0069 {
0070     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->dataColumn(section);
0071 
0072     return QVariant();
0073 }
0074 
0075 QModelIndex AbstractTreeModel::index(int row, int column, const QModelIndex &parent) const
0076 {
0077     std::shared_ptr<TreeItem> parentItem;
0078 
0079     if (!parent.isValid())
0080         parentItem = rootItem;
0081     else
0082         parentItem = getItemById(int(parent.internalId()));
0083 
0084     if (row >= parentItem->childCount()) return QModelIndex();
0085 
0086     std::shared_ptr<TreeItem> childItem = parentItem->child(row);
0087     if (childItem) return createIndex(row, column, quintptr(childItem->getId()));
0088 
0089     return {};
0090 }
0091 
0092 QModelIndex AbstractTreeModel::parent(const QModelIndex &index) const
0093 {
0094     if (!index.isValid()) return {};
0095 
0096     std::shared_ptr<TreeItem> childItem = getItemById(int(index.internalId()));
0097     std::shared_ptr<TreeItem> parentItem = childItem->parentItem().lock();
0098 
0099     Q_ASSERT(parentItem);
0100 
0101     if (parentItem == rootItem) return QModelIndex();
0102 
0103     return createIndex(parentItem->row(), 0, quintptr(parentItem->getId()));
0104 }
0105 
0106 int AbstractTreeModel::rowCount(const QModelIndex &parent) const
0107 {
0108     if (parent.column() > 0) return 0;
0109 
0110     std::shared_ptr<TreeItem> parentItem;
0111     if (!parent.isValid())
0112         parentItem = rootItem;
0113     else
0114         parentItem = getItemById(int(parent.internalId()));
0115 
0116     return parentItem->childCount();
0117 }
0118 
0119 QModelIndex AbstractTreeModel::getIndexFromItem(const std::shared_ptr<TreeItem> &item, int column) const
0120 {
0121     if (item == rootItem) {
0122         return QModelIndex();
0123     }
0124     if (auto ptr = item->parentItem().lock()) {
0125         auto parentIndex = getIndexFromItem(ptr);
0126         return index(item->row(), column, parentIndex);
0127     }
0128     return QModelIndex();
0129 }
0130 
0131 QModelIndex AbstractTreeModel::getIndexFromId(int id) const
0132 {
0133     if (id == rootItem->getId()) {
0134         return QModelIndex();
0135     }
0136     Q_ASSERT(m_allItems.count(id) > 0);
0137     if (auto ptr = m_allItems.at(id).lock()) return getIndexFromItem(ptr);
0138 
0139     Q_ASSERT(false);
0140     return {};
0141 }
0142 
0143 void AbstractTreeModel::notifyRowAboutToAppend(const std::shared_ptr<TreeItem> &item)
0144 {
0145     auto index = getIndexFromItem(item);
0146     beginInsertRows(index, item->childCount(), item->childCount());
0147 }
0148 
0149 void AbstractTreeModel::notifyRowAppended(const std::shared_ptr<TreeItem> &row)
0150 {
0151     Q_UNUSED(row);
0152     endInsertRows();
0153 }
0154 
0155 void AbstractTreeModel::notifyRowAboutToDelete(std::shared_ptr<TreeItem> item, int row)
0156 {
0157     auto index = getIndexFromItem(item);
0158     beginRemoveRows(index, row, row);
0159 }
0160 
0161 void AbstractTreeModel::notifyRowDeleted()
0162 {
0163     endRemoveRows();
0164 }
0165 
0166 // static
0167 int AbstractTreeModel::getNextId()
0168 {
0169     return currentTreeId++;
0170 }
0171 
0172 void AbstractTreeModel::registerItem(const std::shared_ptr<TreeItem> &item)
0173 {
0174     int id = item->getId();
0175     Q_ASSERT(m_allItems.count(id) == 0);
0176     m_allItems[id] = item;
0177 }
0178 
0179 void AbstractTreeModel::deregisterItem(int id, TreeItem *item)
0180 {
0181     Q_UNUSED(item);
0182     Q_ASSERT(m_allItems.count(id) > 0);
0183     m_allItems.erase(id);
0184 }
0185 
0186 std::shared_ptr<TreeItem> AbstractTreeModel::getItemById(int id) const
0187 {
0188     if (id == rootItem->getId()) {
0189         return rootItem;
0190     }
0191     Q_ASSERT(m_allItems.count(id) > 0);
0192     return m_allItems.at(id).lock();
0193 }
0194 
0195 std::shared_ptr<TreeItem> AbstractTreeModel::getRoot() const
0196 {
0197     return rootItem;
0198 }
0199 
0200 bool AbstractTreeModel::checkConsistency()
0201 {
0202     // first check that the root is all good
0203     if (!rootItem || !rootItem->m_isRoot || !rootItem->isInModel() || m_allItems.count(rootItem->getId()) == 0) {
0204         qDebug() << !rootItem->m_isRoot << !rootItem->isInModel() << (m_allItems.count(rootItem->getId()) == 0);
0205         qDebug() << "ERROR: Model is not valid because root is not properly constructed";
0206         return false;
0207     }
0208     // Then we traverse the tree from the root, checking the infos on the way
0209     std::unordered_set<int> seenIDs;
0210     std::queue<std::pair<int, std::pair<int, int>>> queue; // store (id, (depth, parentId))
0211     queue.push({rootItem->getId(), {0, rootItem->getId()}});
0212     while (!queue.empty()) {
0213         auto current = queue.front();
0214         int currentId = current.first, currentDepth = current.second.first;
0215         int parentId = current.second.second;
0216         queue.pop();
0217         if (seenIDs.count(currentId) != 0) {
0218             qDebug() << "ERROR: Invalid tree: Id found twice."
0219                      << "It either a cycle or a clash in id attribution";
0220             return false;
0221         }
0222         if (m_allItems.count(currentId) == 0) {
0223             qDebug() << "ERROR: Invalid tree: Id not found. Item is not registered";
0224             return false;
0225         }
0226         auto currentItem = m_allItems[currentId].lock();
0227         if (currentItem->depth() != currentDepth) {
0228             qDebug() << "ERROR: Invalid tree: invalid depth info found";
0229             return false;
0230         }
0231         if (!currentItem->isInModel()) {
0232             qDebug() << "ERROR: Invalid tree: item thinks it is not in a model";
0233             return false;
0234         }
0235         if (currentId != rootItem->getId()) {
0236             if ((currentDepth == 0 || currentItem->m_isRoot)) {
0237                 qDebug() << "ERROR: Invalid tree: duplicate root";
0238                 return false;
0239             }
0240             if (auto ptr = currentItem->parentItem().lock()) {
0241                 if (ptr->getId() != parentId || ptr->child(currentItem->row())->getId() != currentItem->getId()) {
0242                     qDebug() << "ERROR: Invalid tree: invalid parent link";
0243                     return false;
0244                 }
0245             } else {
0246                 qDebug() << "ERROR: Invalid tree: invalid parent";
0247                 return false;
0248             }
0249         }
0250         // propagate to children
0251         int i = 0;
0252         for (const auto &child : currentItem->m_childItems) {
0253             if (currentItem->child(i) != child) {
0254                 qDebug() << "ERROR: Invalid tree: invalid child ordering";
0255                 return false;
0256             }
0257             queue.push({child->getId(), {currentDepth + 1, currentId}});
0258             i++;
0259         }
0260     }
0261     return true;
0262 }
0263 
0264 Fun AbstractTreeModel::addItem_lambda(const std::shared_ptr<TreeItem> &new_item, int parentId)
0265 {
0266     return [this, new_item, parentId]() {
0267         if (new_item->m_isInvalid) {
0268             return true;
0269         }
0270         /* Insertion is simply setting the parent of the item.*/
0271         std::shared_ptr<TreeItem> parent;
0272         if (parentId != -1) {
0273             parent = getItemById(parentId);
0274             if (!parent) {
0275                 Q_ASSERT(parent);
0276                 return false;
0277             }
0278         }
0279         return new_item->changeParent(parent);
0280     };
0281 }
0282 
0283 Fun AbstractTreeModel::removeItem_lambda(int id)
0284 {
0285     return [this, id]() {
0286         /* Deletion simply deregister clip and remove it from parent.
0287            The actual object is not actually deleted, because a shared_pointer to it
0288            is captured by the reverse operation.
0289            Actual deletions occurs when the undo object is destroyed.
0290         */
0291         if (m_allItems.count(id) == 0) {
0292             // Invalid item, might have been deleted on insert
0293             return true;
0294         }
0295         auto item = m_allItems[id].lock();
0296         Q_ASSERT(item);
0297         if (!item) {
0298             return false;
0299         }
0300         auto parent = item->parentItem().lock();
0301         parent->removeChild(item);
0302         return true;
0303     };
0304 }
0305 
0306 Fun AbstractTreeModel::moveItem_lambda(int id, int destRow, bool force)
0307 {
0308     Fun lambda = []() { return true; };
0309 
0310     std::vector<std::shared_ptr<TreeItem>> oldStack;
0311     auto item = getItemById(id);
0312     if (!force && item->row() == destRow) {
0313         // nothing to do
0314         return lambda;
0315     }
0316     if (auto parent = item->parentItem().lock()) {
0317         if (destRow > parent->childCount() || destRow < 0) {
0318             return []() { return false; };
0319         }
0320         int parentId = parent->getId();
0321         // remove the element to move
0322         oldStack.push_back(item);
0323         Fun oper = removeItem_lambda(id);
0324         PUSH_LAMBDA(oper, lambda);
0325         // remove the tail of the stack
0326         for (int i = destRow; i < parent->childCount(); ++i) {
0327             auto current = parent->child(i);
0328             if (current->getId() != id) {
0329                 oldStack.push_back(current);
0330                 oper = removeItem_lambda(current->getId());
0331                 PUSH_LAMBDA(oper, lambda);
0332             }
0333         }
0334         // insert back in order
0335         for (const auto &elem : oldStack) {
0336             oper = addItem_lambda(elem, parentId);
0337             PUSH_LAMBDA(oper, lambda);
0338         }
0339         return lambda;
0340     }
0341     return []() { return false; };
0342 }