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 }