File indexing completed on 2024-04-28 04:52:27
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "groupsmodel.hpp" 0007 #include "macros.hpp" 0008 #include "timelineitemmodel.hpp" 0009 #include "trackmodel.hpp" 0010 #include <QDebug> 0011 #include <QJsonArray> 0012 #include <QJsonDocument> 0013 #include <QJsonObject> 0014 #include <QModelIndex> 0015 #include <queue> 0016 #include <stack> 0017 #include <utility> 0018 0019 GroupsModel::GroupsModel(std::weak_ptr<TimelineItemModel> parent) 0020 : m_parent(std::move(parent)) 0021 , m_lock(QReadWriteLock::Recursive) 0022 { 0023 } 0024 0025 void GroupsModel::promoteToGroup(int gid, GroupType type) 0026 { 0027 Q_ASSERT(type != GroupType::Leaf); 0028 Q_ASSERT(m_groupIds.count(gid) == 0); 0029 m_groupIds.insert({gid, type}); 0030 0031 auto ptr = m_parent.lock(); 0032 if (ptr) { 0033 // qDebug() << "Registering group" << gid << "of type" << groupTypeToStr(getType(gid)); 0034 ptr->registerGroup(gid); 0035 } else { 0036 qDebug() << "Impossible to create group because the timeline is not available anymore"; 0037 Q_ASSERT(false); 0038 } 0039 } 0040 void GroupsModel::downgradeToLeaf(int gid) 0041 { 0042 Q_ASSERT(m_groupIds.count(gid) != 0); 0043 Q_ASSERT(m_downLink.at(gid).size() == 0); 0044 auto ptr = m_parent.lock(); 0045 if (ptr) { 0046 // qDebug() << "Deregistering group" << gid << "of type" << groupTypeToStr(getType(gid)); 0047 ptr->deregisterGroup(gid); 0048 m_groupIds.erase(gid); 0049 } else { 0050 qDebug() << "Impossible to ungroup item because the timeline is not available anymore"; 0051 Q_ASSERT(false); 0052 } 0053 } 0054 0055 Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids, GroupType type, int parent) 0056 { 0057 QWriteLocker locker(&m_lock); 0058 Q_ASSERT(ids.size() == 0 || type != GroupType::Leaf); 0059 return [gid, ids, parent, type, this]() { 0060 createGroupItem(gid); 0061 if (parent != -1) { 0062 setGroup(gid, parent); 0063 } 0064 0065 if (ids.size() > 0) { 0066 promoteToGroup(gid, type); 0067 std::unordered_set<int> roots; 0068 std::transform(ids.begin(), ids.end(), std::inserter(roots, roots.begin()), [&](int id) { return getRootId(id); }); 0069 auto ptr = m_parent.lock(); 0070 if (!ptr) Q_ASSERT(false); 0071 for (int id : roots) { 0072 if (type != GroupType::Selection) { 0073 setGroup(getRootId(id), gid, true); 0074 } else { 0075 setGroup(getRootId(id), gid, false); 0076 ptr->setSelected(id, true); 0077 } 0078 } 0079 } 0080 return true; 0081 }; 0082 } 0083 0084 int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type, bool force) 0085 { 0086 QWriteLocker locker(&m_lock); 0087 Q_ASSERT(type != GroupType::Leaf); 0088 Q_ASSERT(!ids.empty()); 0089 std::unordered_set<int> roots; 0090 std::transform(ids.begin(), ids.end(), std::inserter(roots, roots.begin()), [&](int id) { return getRootId(id); }); 0091 if (roots.size() == 1 && !force) { 0092 // We do not create a group with only one element. Instead, we return the id of that element 0093 return *(roots.begin()); 0094 } 0095 0096 int gid = TimelineModel::getNextId(); 0097 auto operation = groupItems_lambda(gid, roots, type); 0098 if (operation()) { 0099 auto reverse = destructGroupItem_lambda(gid); 0100 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0101 return gid; 0102 } 0103 return -1; 0104 } 0105 0106 bool GroupsModel::ungroupItem(int id, Fun &undo, Fun &redo, bool deleteOrphan) 0107 { 0108 QWriteLocker locker(&m_lock); 0109 int gid = getRootId(id); 0110 if (m_groupIds.count(gid) == 0) { 0111 // element is not part of a group 0112 return false; 0113 } 0114 0115 return destructGroupItem(gid, deleteOrphan, undo, redo); 0116 } 0117 0118 void GroupsModel::createGroupItem(int id) 0119 { 0120 QWriteLocker locker(&m_lock); 0121 Q_ASSERT(m_upLink.count(id) == 0); 0122 Q_ASSERT(m_downLink.count(id) == 0); 0123 m_upLink[id] = -1; 0124 m_downLink[id] = std::unordered_set<int>(); 0125 } 0126 0127 Fun GroupsModel::destructGroupItem_lambda(int id) 0128 { 0129 QWriteLocker locker(&m_lock); 0130 return [this, id]() { 0131 removeFromGroup(id); 0132 auto ptr = m_parent.lock(); 0133 if (!ptr) Q_ASSERT(false); 0134 for (int child : m_downLink[id]) { 0135 m_upLink[child] = -1; 0136 QModelIndex ix; 0137 if (ptr->isClip(child)) { 0138 ix = ptr->makeClipIndexFromID(child); 0139 } else if (ptr->isComposition(child)) { 0140 ix = ptr->makeCompositionIndexFromID(child); 0141 } 0142 if (ix.isValid()) { 0143 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); 0144 } else if (ptr->isSubTitle(child)) { 0145 ptr->subtitleChanged(child, {TimelineModel::GroupedRole}); 0146 } 0147 } 0148 m_downLink[id].clear(); 0149 if (getType(id) != GroupType::Leaf) { 0150 downgradeToLeaf(id); 0151 } 0152 m_downLink.erase(id); 0153 m_upLink.erase(id); 0154 return true; 0155 }; 0156 } 0157 0158 bool GroupsModel::destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo) 0159 { 0160 QWriteLocker locker(&m_lock); 0161 Q_ASSERT(m_upLink.count(id) > 0); 0162 int parent = m_upLink[id]; 0163 auto old_children = m_downLink[id]; 0164 auto old_type = getType(id); 0165 auto old_parent_type = GroupType::Normal; 0166 if (parent != -1) { 0167 old_parent_type = getType(parent); 0168 } 0169 auto operation = destructGroupItem_lambda(id); 0170 if (operation()) { 0171 auto reverse = groupItems_lambda(id, old_children, old_type, parent); 0172 // we may need to reset the group of the parent 0173 if (parent != -1) { 0174 auto setParent = [&, old_parent_type, parent]() { 0175 setType(parent, old_parent_type); 0176 return true; 0177 }; 0178 PUSH_LAMBDA(setParent, reverse); 0179 } 0180 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0181 if (parent != -1 && m_downLink[parent].empty() && deleteOrphan) { 0182 return destructGroupItem(parent, true, undo, redo); 0183 } 0184 return true; 0185 } 0186 return false; 0187 } 0188 0189 bool GroupsModel::destructGroupItem(int id) 0190 { 0191 QWriteLocker locker(&m_lock); 0192 return destructGroupItem_lambda(id)(); 0193 } 0194 0195 int GroupsModel::getRootId(int id) const 0196 { 0197 READ_LOCK(); 0198 std::unordered_set<int> seen; // we store visited ids to detect cycles 0199 int father = -1; 0200 do { 0201 Q_ASSERT(m_upLink.count(id) > 0); 0202 Q_ASSERT(seen.count(id) == 0); 0203 seen.insert(id); 0204 father = m_upLink.at(id); 0205 if (father != -1) { 0206 id = father; 0207 } 0208 } while (father != -1); 0209 return id; 0210 } 0211 0212 bool GroupsModel::isLeaf(int id) const 0213 { 0214 READ_LOCK(); 0215 Q_ASSERT(m_downLink.count(id) > 0); 0216 return m_downLink.at(id).empty(); 0217 } 0218 0219 bool GroupsModel::isInGroup(int id) const 0220 { 0221 READ_LOCK(); 0222 Q_ASSERT(m_downLink.count(id) > 0); 0223 return getRootId(id) != id; 0224 } 0225 0226 int GroupsModel::getSplitPartner(int id) const 0227 { 0228 READ_LOCK(); 0229 Q_ASSERT(m_downLink.count(id) > 0); 0230 int groupId = m_upLink.at(id); 0231 if (groupId == -1 || getType(groupId) != GroupType::AVSplit) { 0232 // clip does not have an AV split partner 0233 return -1; 0234 } 0235 std::unordered_set<int> leaves = getDirectChildren(groupId); 0236 if (leaves.size() != 2) { 0237 // clip does not have an AV split partner 0238 qDebug() << "WRONG SPLIT GROUP SIZE: " << leaves.size(); 0239 return -1; 0240 } 0241 for (const int &child : leaves) { 0242 if (child != id) { 0243 return child; 0244 } 0245 } 0246 return -1; 0247 } 0248 0249 std::unordered_set<int> GroupsModel::getSubtree(int id) const 0250 { 0251 READ_LOCK(); 0252 std::unordered_set<int> result; 0253 result.insert(id); 0254 std::queue<int> queue; 0255 queue.push(id); 0256 while (!queue.empty()) { 0257 int current = queue.front(); 0258 queue.pop(); 0259 for (const int &child : m_downLink.at(current)) { 0260 result.insert(child); 0261 queue.push(child); 0262 } 0263 } 0264 return result; 0265 } 0266 0267 std::unordered_set<int> GroupsModel::getLeaves(int id) const 0268 { 0269 READ_LOCK(); 0270 std::unordered_set<int> result; 0271 std::queue<int> queue; 0272 queue.push(id); 0273 while (!queue.empty()) { 0274 int current = queue.front(); 0275 queue.pop(); 0276 for (const int &child : m_downLink.at(current)) { 0277 queue.push(child); 0278 } 0279 if (m_downLink.at(current).empty()) { 0280 result.insert(current); 0281 } 0282 } 0283 return result; 0284 } 0285 0286 std::unordered_set<int> GroupsModel::getDirectChildren(int id) const 0287 { 0288 READ_LOCK(); 0289 Q_ASSERT(m_downLink.count(id) > 0); 0290 return m_downLink.at(id); 0291 } 0292 int GroupsModel::getDirectAncestor(int id) const 0293 { 0294 READ_LOCK(); 0295 Q_ASSERT(m_upLink.count(id) > 0); 0296 return m_upLink.at(id); 0297 } 0298 0299 void GroupsModel::setGroup(int id, int groupId, bool changeState) 0300 { 0301 QWriteLocker locker(&m_lock); 0302 Q_ASSERT(m_upLink.count(id) > 0); 0303 Q_ASSERT(groupId == -1 || m_downLink.count(groupId) > 0); 0304 Q_ASSERT(id != groupId); 0305 removeFromGroup(id); 0306 m_upLink[id] = groupId; 0307 if (groupId != -1) { 0308 m_downLink[groupId].insert(id); 0309 auto ptr = m_parent.lock(); 0310 if (changeState && ptr) { 0311 QModelIndex ix; 0312 if (ptr->isClip(id)) { 0313 ix = ptr->makeClipIndexFromID(id); 0314 } else if (ptr->isComposition(id)) { 0315 ix = ptr->makeCompositionIndexFromID(id); 0316 } 0317 if (ix.isValid()) { 0318 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); 0319 } else if (ptr->isSubTitle(id)) { 0320 ptr->subtitleChanged(id, {TimelineModel::GroupedRole}); 0321 } 0322 } 0323 if (getType(groupId) == GroupType::Leaf) { 0324 promoteToGroup(groupId, GroupType::Normal); 0325 } 0326 } 0327 } 0328 0329 QString GroupsModel::debugString() 0330 { 0331 QString string; 0332 for (const auto &item : m_downLink) { 0333 QStringList leafs; 0334 for (auto leaf : item.second) { 0335 leafs << QString::number(leaf); 0336 } 0337 string.append((QStringLiteral("ID: %1 Leafs: %2; ").arg(item.first).arg(leafs.join(" ")))); 0338 } 0339 return string; 0340 } 0341 0342 void GroupsModel::removeFromGroup(int id) 0343 { 0344 QWriteLocker locker(&m_lock); 0345 Q_ASSERT(m_upLink.count(id) > 0); 0346 Q_ASSERT(m_downLink.count(id) > 0); 0347 int parent = m_upLink[id]; 0348 if (parent != -1) { 0349 Q_ASSERT(getType(parent) != GroupType::Leaf); 0350 m_downLink[parent].erase(id); 0351 QModelIndex ix; 0352 auto ptr = m_parent.lock(); 0353 if (!ptr) Q_ASSERT(false); 0354 if (ptr->isClip(id)) { 0355 ix = ptr->makeClipIndexFromID(id); 0356 } else if (ptr->isComposition(id)) { 0357 ix = ptr->makeCompositionIndexFromID(id); 0358 } 0359 if (ix.isValid()) { 0360 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); 0361 } else if (ptr->isSubTitle(id)) { 0362 ptr->subtitleChanged(id, {TimelineModel::GroupedRole}); 0363 } 0364 if (m_downLink[parent].size() == 0) { 0365 downgradeToLeaf(parent); 0366 } 0367 } 0368 m_upLink[id] = -1; 0369 } 0370 0371 bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo) 0372 { 0373 // The idea is as follow: we start from the leaves, and go up to the root. 0374 // In the process, if we find a node with only one children, we flag it for deletion 0375 QWriteLocker locker(&m_lock); 0376 Q_ASSERT(m_upLink.count(id) > 0); 0377 auto leaves = getLeaves(id); 0378 std::unordered_map<int, int> old_parents, new_parents; 0379 std::vector<int> to_delete; 0380 std::unordered_set<int> processed; // to avoid going twice along the same branch 0381 for (int leaf : leaves) { 0382 int current = m_upLink[leaf]; 0383 int start = leaf; 0384 while (current != m_upLink[id] && processed.count(current) == 0) { 0385 processed.insert(current); 0386 if (m_downLink[current].size() == 1) { 0387 to_delete.push_back(current); 0388 } else { 0389 if (current != m_upLink[start]) { 0390 old_parents[start] = m_upLink[start]; 0391 new_parents[start] = current; 0392 } 0393 start = current; 0394 } 0395 current = m_upLink[current]; 0396 } 0397 if (current != m_upLink[start]) { 0398 old_parents[start] = m_upLink[start]; 0399 new_parents[start] = current; 0400 } 0401 } 0402 auto parent_changer = [this](const std::unordered_map<int, int> &parents) { 0403 auto ptr = m_parent.lock(); 0404 if (!ptr) { 0405 qDebug() << "Impossible to create group because the timeline is not available anymore"; 0406 return false; 0407 } 0408 for (const auto &group : parents) { 0409 setGroup(group.first, group.second); 0410 } 0411 return true; 0412 }; 0413 Fun reverse = [old_parents, parent_changer]() { return parent_changer(old_parents); }; 0414 Fun operation = [new_parents, parent_changer]() { return parent_changer(new_parents); }; 0415 bool res = operation(); 0416 if (!res) { 0417 bool undone = reverse(); 0418 Q_ASSERT(undone); 0419 return res; 0420 } 0421 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0422 0423 for (int gid : to_delete) { 0424 Q_ASSERT(m_downLink[gid].size() == 0); 0425 if (getType(gid) == GroupType::Selection) { 0426 continue; 0427 } 0428 res = destructGroupItem(gid, false, undo, redo); 0429 if (!res) { 0430 bool undone = undo(); 0431 Q_ASSERT(undone); 0432 return res; 0433 } 0434 } 0435 return true; 0436 } 0437 0438 bool GroupsModel::split(int id, const std::function<bool(int)> &criterion, Fun &undo, Fun &redo) 0439 { 0440 QWriteLocker locker(&m_lock); 0441 if (isLeaf(id)) { 0442 return true; 0443 } 0444 // This function is valid only for roots (otherwise it is not clear what should be the new parent of the created tree) 0445 Q_ASSERT(m_upLink[id] == -1); 0446 Q_ASSERT(m_groupIds[id] != GroupType::Selection); 0447 bool regroup = true; // we don't support splitting if selection group is active 0448 // We do a BFS on the tree to copy it 0449 // We store corresponding nodes 0450 std::unordered_map<int, int> corresp; // keys are id in the original tree, values are temporary negative id assigned for creation of the new tree 0451 corresp[-1] = -1; 0452 // These are the nodes to be moved to new tree 0453 std::vector<int> to_move; 0454 // We store the groups (ie the nodes) that are going to be part of the new tree 0455 // Keys are temporary id (negative) and values are the set of children (true ids in the case of leaves and temporary ids for other nodes) 0456 std::unordered_map<int, std::unordered_set<int>> new_groups; 0457 // We store also the target type of the new groups 0458 std::unordered_map<int, GroupType> new_types; 0459 std::queue<int> queue; 0460 queue.push(id); 0461 int tempId = -10; 0462 while (!queue.empty()) { 0463 int current = queue.front(); 0464 queue.pop(); 0465 if (!isLeaf(current) || criterion(current)) { 0466 if (isLeaf(current)) { 0467 to_move.push_back(current); 0468 new_groups[corresp[m_upLink[current]]].insert(current); 0469 } else { 0470 corresp[current] = tempId; 0471 new_types[tempId] = getType(current); 0472 if (m_upLink[current] != -1) new_groups[corresp[m_upLink[current]]].insert(tempId); 0473 tempId--; 0474 } 0475 } 0476 for (const int &child : m_downLink.at(current)) { 0477 queue.push(child); 0478 } 0479 } 0480 // First, we simulate deletion of elements that we have to remove from the original tree 0481 // A side effect of this is that empty groups will be removed 0482 for (const auto &leaf : to_move) { 0483 destructGroupItem(leaf, true, undo, redo); 0484 } 0485 0486 // we artificially recreate the leaves 0487 Fun operation = [this, to_move]() { 0488 for (const auto &leaf : to_move) { 0489 createGroupItem(leaf); 0490 } 0491 return true; 0492 }; 0493 Fun reverse = [this, to_move]() { 0494 for (const auto &group : to_move) { 0495 destructGroupItem(group); 0496 } 0497 return true; 0498 }; 0499 bool res = operation(); 0500 if (!res) { 0501 return false; 0502 } 0503 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0504 0505 // We prune the new_groups to remove empty ones 0506 bool finished = false; 0507 while (!finished) { 0508 finished = true; 0509 int selected = INT_MAX; 0510 for (const auto &it : new_groups) { 0511 if (it.second.size() == 0) { // empty group 0512 finished = false; 0513 selected = it.first; 0514 break; 0515 } 0516 for (int it2 : it.second) { 0517 if (it2 < -1 && new_groups.count(it2) == 0) { 0518 // group that has no reference, it is empty too 0519 finished = false; 0520 selected = it2; 0521 break; 0522 } 0523 } 0524 if (!finished) break; 0525 } 0526 if (!finished) { 0527 new_groups.erase(selected); 0528 for (auto &new_group : new_groups) { 0529 new_group.second.erase(selected); 0530 } 0531 } 0532 } 0533 // We now regroup the items of the new tree to recreate hierarchy. 0534 // This is equivalent to creating the tree bottom up (starting from the leaves) 0535 // At each iteration, we create a new node by grouping together elements that are either leaves or already created nodes. 0536 std::unordered_map<int, int> created_id; // to keep track of node that we create 0537 0538 std::vector<int> newGroups; 0539 while (!new_groups.empty()) { 0540 int selected = INT_MAX; 0541 for (const auto &group : new_groups) { 0542 // we check that all children are already created 0543 bool ok = true; 0544 for (int elem : group.second) { 0545 if (elem < -1 && created_id.count(elem) == 0) { 0546 ok = false; 0547 break; 0548 } 0549 } 0550 if (ok) { 0551 selected = group.first; 0552 break; 0553 } 0554 } 0555 Q_ASSERT(selected != INT_MAX); 0556 std::unordered_set<int> group; 0557 for (int elem : new_groups[selected]) { 0558 group.insert(elem < -1 ? created_id[elem] : elem); 0559 } 0560 Q_ASSERT(new_types.count(selected) != 0); 0561 int gid = groupItems(group, undo, redo, new_types[selected], true); 0562 newGroups.push_back(gid); 0563 created_id[selected] = gid; 0564 new_groups.erase(selected); 0565 } 0566 0567 if (regroup) { 0568 if (m_groupIds.count(id) > 0) { 0569 mergeSingleGroups(id, undo, redo); 0570 } 0571 if (created_id[corresp[id]]) { 0572 mergeSingleGroups(created_id[corresp[id]], undo, redo); 0573 } 0574 } 0575 Fun clear_group_selection = [this, newGroups]() { 0576 if (auto ptr = m_parent.lock()) { 0577 // Ensure one of the deleted group was not selected 0578 ptr->clearGroupSelectionOnDelete(newGroups); 0579 } 0580 return true; 0581 }; 0582 PUSH_FRONT_LAMBDA(clear_group_selection, undo); 0583 0584 return res; 0585 } 0586 0587 void GroupsModel::setInGroupOf(int id, int targetId, Fun &undo, Fun &redo) 0588 { 0589 QWriteLocker locker(&m_lock); 0590 Q_ASSERT(m_upLink.count(targetId) > 0); 0591 Fun operation = [this, id, group = m_upLink[targetId]]() { 0592 setGroup(id, group); 0593 return true; 0594 }; 0595 Fun reverse = [this, id, group = m_upLink[id]]() { 0596 setGroup(id, group); 0597 return true; 0598 }; 0599 operation(); 0600 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0601 } 0602 0603 bool GroupsModel::createGroupAtSameLevel(int id, std::unordered_set<int> to_add, GroupType type, Fun &undo, Fun &redo) 0604 { 0605 QWriteLocker locker(&m_lock); 0606 Q_ASSERT(m_upLink.count(id) > 0); 0607 Q_ASSERT(isLeaf(id)); 0608 if (to_add.size() == 0) { 0609 return true; 0610 } 0611 int gid = TimelineModel::getNextId(); 0612 std::unordered_map<int, int> old_parents; 0613 to_add.insert(id); 0614 0615 for (int g : to_add) { 0616 Q_ASSERT(m_upLink.count(g) > 0); 0617 old_parents[g] = m_upLink[g]; 0618 } 0619 Fun operation = [this, gid, type, to_add, parent = m_upLink.at(id)]() { 0620 createGroupItem(gid); 0621 setGroup(gid, parent); 0622 for (const auto &g : to_add) { 0623 setGroup(g, gid); 0624 } 0625 setType(gid, type); 0626 return true; 0627 }; 0628 Fun reverse = [this, old_parents, gid]() { 0629 for (const auto &g : old_parents) { 0630 setGroup(g.first, g.second); 0631 } 0632 setGroup(gid, -1); 0633 destructGroupItem_lambda(gid)(); 0634 return true; 0635 }; 0636 bool success = operation(); 0637 if (success) { 0638 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0639 } 0640 return success; 0641 } 0642 0643 bool GroupsModel::processCopy(int gid, std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo) 0644 { 0645 qDebug() << "processCopy" << gid; 0646 if (isLeaf(gid)) { 0647 qDebug() << "it is a leaf"; 0648 return true; 0649 } 0650 bool ok = true; 0651 std::unordered_set<int> targetGroup; 0652 for (int child : m_downLink.at(gid)) { 0653 ok = ok && processCopy(child, mapping, undo, redo); 0654 if (!ok) { 0655 break; 0656 } 0657 targetGroup.insert(mapping.at(child)); 0658 } 0659 qDebug() << "processCopy" << gid << "success of child" << ok; 0660 if (ok && m_groupIds[gid] != GroupType::Selection) { 0661 int id = groupItems(targetGroup, undo, redo); 0662 qDebug() << "processCopy" << gid << "created id" << id; 0663 if (id != -1) { 0664 mapping[gid] = id; 0665 return true; 0666 } 0667 } 0668 return ok; 0669 } 0670 0671 bool GroupsModel::copyGroups(std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo) 0672 { 0673 Fun local_undo = []() { return true; }; 0674 Fun local_redo = []() { return true; }; 0675 // destruct old groups for the targets items 0676 for (const auto &corresp : mapping) { 0677 ungroupItem(corresp.second, local_undo, local_redo); 0678 } 0679 std::unordered_set<int> roots; 0680 std::transform(mapping.begin(), mapping.end(), std::inserter(roots, roots.begin()), 0681 [&](decltype(*mapping.begin()) corresp) { return getRootId(corresp.first); }); 0682 bool res = true; 0683 qDebug() << "found" << roots.size() << "roots"; 0684 for (int r : roots) { 0685 qDebug() << "processing copy for root " << r; 0686 res = res && processCopy(r, mapping, local_undo, local_redo); 0687 if (!res) { 0688 bool undone = local_undo(); 0689 Q_ASSERT(undone); 0690 return false; 0691 } 0692 } 0693 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 0694 return true; 0695 } 0696 0697 GroupType GroupsModel::getType(int id) const 0698 { 0699 if (m_groupIds.count(id) > 0) { 0700 return m_groupIds.at(id); 0701 } 0702 return GroupType::Leaf; 0703 } 0704 0705 QJsonObject GroupsModel::toJson(int gid) const 0706 { 0707 QJsonObject currentGroup; 0708 currentGroup.insert(QLatin1String("type"), QJsonValue(groupTypeToStr(getType(gid)))); 0709 if (m_groupIds.count(gid) > 0) { 0710 // in that case, we have a proper group 0711 QJsonArray array; 0712 Q_ASSERT(m_downLink.count(gid) > 0); 0713 for (int c : m_downLink.at(gid)) { 0714 array.push_back(toJson(c)); 0715 } 0716 currentGroup.insert(QLatin1String("children"), array); 0717 } else { 0718 // in that case we have a clip or composition 0719 if (auto ptr = m_parent.lock()) { 0720 Q_ASSERT(ptr->isClip(gid) || ptr->isComposition(gid) || ptr->isSubTitle(gid)); 0721 currentGroup.insert(QLatin1String("leaf"), 0722 QJsonValue(QLatin1String(ptr->isClip(gid) ? "clip" : ptr->isComposition(gid) ? "composition" : "subtitle"))); 0723 int track = ptr->isSubTitle(gid) ? -2 : ptr->getTrackPosition(ptr->getItemTrackId(gid)); 0724 int pos = ptr->getItemPosition(gid); 0725 currentGroup.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(track).arg(pos))); 0726 } else { 0727 qDebug() << "Impossible to create group because the timeline is not available anymore"; 0728 Q_ASSERT(false); 0729 } 0730 } 0731 return currentGroup; 0732 } 0733 0734 const QString GroupsModel::toJson() const 0735 { 0736 std::unordered_set<int> roots; 0737 std::transform(m_groupIds.begin(), m_groupIds.end(), std::inserter(roots, roots.begin()), [&](decltype(*m_groupIds.begin()) g) { 0738 const int parentId = getRootId(g.first); 0739 if (getType(parentId) == GroupType::Selection) { 0740 // Don't insert selection group, only its child groups 0741 return g.first; 0742 } 0743 return parentId; 0744 }); 0745 QJsonArray list; 0746 for (int r : roots) { 0747 list.push_back(toJson(r)); 0748 } 0749 QJsonDocument json(list); 0750 return QString(json.toJson()); 0751 } 0752 0753 const QString GroupsModel::toJson(const std::unordered_set<int> &roots) const 0754 { 0755 QJsonArray list; 0756 for (int r : roots) { 0757 if (getType(r) != GroupType::Selection) list.push_back(toJson(r)); 0758 } 0759 QJsonDocument json(list); 0760 return QString(json.toJson()); 0761 } 0762 0763 int GroupsModel::fromJson(const QJsonObject &o, Fun &undo, Fun &redo) 0764 { 0765 if (!o.contains(QLatin1String("type"))) { 0766 qDebug() << "CANNOT PARSE GROUP DATA"; 0767 return -1; 0768 } 0769 auto type = groupTypeFromStr(o.value(QLatin1String("type")).toString()); 0770 if (type == GroupType::Leaf) { 0771 if (auto ptr = m_parent.lock()) { 0772 if (!o.contains(QLatin1String("data")) || !o.contains(QLatin1String("leaf"))) { 0773 qDebug() << "Error: missing info in the group structure while parsing json"; 0774 return -1; 0775 } 0776 QString data = o.value(QLatin1String("data")).toString(); 0777 QString leaf = o.value(QLatin1String("leaf")).toString(); 0778 int trackPos = data.section(":", 0, 0).toInt(); 0779 int trackId = trackPos > -1 ? ptr->getTrackIndexFromPosition(trackPos) : -1; 0780 int pos = data.section(":", 1, 1).toInt(); 0781 int id = -1; 0782 if (leaf == QLatin1String("clip")) { 0783 id = ptr->getClipByStartPosition(trackId, pos); 0784 } else if (leaf == QLatin1String("composition")) { 0785 id = ptr->getCompositionByPosition(trackId, pos); 0786 } else if (leaf == QLatin1String("subtitle")) { 0787 id = ptr->getSubtitleByStartPosition(pos); 0788 } else { 0789 qDebug() << " * * *UNKNOWN ITEM: " << leaf; 0790 } 0791 return id; 0792 } else { 0793 qDebug() << "Impossible to create group because the timeline is not available anymore"; 0794 Q_ASSERT(false); 0795 } 0796 } else { 0797 if (!o.contains(QLatin1String("children"))) { 0798 qDebug() << "Error: missing info in the group structure while parsing json"; 0799 return -1; 0800 } 0801 auto value = o.value(QLatin1String("children")); 0802 if (!value.isArray()) { 0803 qDebug() << "Error : Expected json array of children while parsing groups"; 0804 return -1; 0805 } 0806 const auto children = value.toArray(); 0807 std::unordered_set<int> ids; 0808 for (const auto &c : children) { 0809 if (!c.isObject()) { 0810 qDebug() << "Error : Expected json object while parsing groups"; 0811 return -1; 0812 } 0813 ids.insert(fromJson(c.toObject(), undo, redo)); 0814 } 0815 if (ids.count(-1) > 0 || type == GroupType::Selection) { 0816 return -1; 0817 } 0818 return groupItems(ids, undo, redo, type); 0819 } 0820 return -1; 0821 } 0822 0823 bool GroupsModel::fromJson(const QString &data) 0824 { 0825 if (data.isEmpty()) { 0826 return true; 0827 } 0828 Fun undo = []() { return true; }; 0829 Fun redo = []() { return true; }; 0830 auto json = QJsonDocument::fromJson(data.toUtf8()); 0831 if (!json.isArray()) { 0832 qDebug() << "Error : Json file should be an array"; 0833 return false; 0834 } 0835 const auto list = json.array(); 0836 bool ok = true; 0837 for (const auto &elem : list) { 0838 if (!elem.isObject()) { 0839 qDebug() << "Error : Expected json object while parsing groups"; 0840 undo(); 0841 return false; 0842 } 0843 ok = ok && fromJson(elem.toObject(), undo, redo); 0844 } 0845 return ok; 0846 } 0847 0848 void GroupsModel::adjustOffset(QJsonArray &updatedNodes, const QJsonObject &childObject, int offset, const QMap<int, int> &trackMap, double ratio) 0849 { 0850 auto value = childObject.value(QLatin1String("children")); 0851 auto children = value.toArray(); 0852 for (const auto &c : qAsConst(children)) { 0853 if (!c.isObject()) { 0854 continue; 0855 } 0856 auto child = c.toObject(); 0857 auto type = groupTypeFromStr(child.value(QLatin1String("type")).toString()); 0858 if (child.contains(QLatin1String("data"))) { 0859 if (auto ptr = m_parent.lock()) { 0860 QString cur_data = child.value(QLatin1String("data")).toString(); 0861 int trackId = cur_data.section(":", 0, 0).toInt(); 0862 int pos = cur_data.section(":", 1, 1).toInt() * ratio; 0863 int trackPos = trackId == -2 ? -2 : ptr->getTrackPosition(trackMap.value(trackId)); 0864 pos += offset; 0865 child.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(trackPos).arg(pos))); 0866 updatedNodes.append(QJsonValue(child)); 0867 } 0868 } else if (type != GroupType::Leaf) { 0869 QJsonObject currentGroup; 0870 currentGroup.insert(QLatin1String("type"), QJsonValue(groupTypeToStr(type))); 0871 QJsonArray array; 0872 adjustOffset(array, child, offset, trackMap); 0873 currentGroup.insert(QLatin1String("children"), array); 0874 updatedNodes.append(QJsonValue(currentGroup)); 0875 } 0876 } 0877 } 0878 0879 bool GroupsModel::fromJsonWithOffset(const QString &data, const QMap<int, int> &trackMap, int offset, double ratio, Fun &undo, Fun &redo) 0880 { 0881 Fun local_undo = []() { return true; }; 0882 Fun local_redo = []() { return true; }; 0883 auto json = QJsonDocument::fromJson(data.toUtf8()); 0884 if (!json.isArray()) { 0885 qDebug() << "Error : Json file should be an array"; 0886 return false; 0887 } 0888 auto list = json.array(); 0889 QJsonArray newGroups; 0890 bool ok = true; 0891 for (const auto &elem : qAsConst(list)) { 0892 if (!elem.isObject()) { 0893 qDebug() << "Error : Expected json object while parsing groups"; 0894 local_undo(); 0895 return false; 0896 } 0897 QJsonObject obj = elem.toObject(); 0898 QJsonArray updatedNodes; 0899 auto type = groupTypeFromStr(obj.value(QLatin1String("type")).toString()); 0900 auto value = obj.value(QLatin1String("children")); 0901 if (!value.isArray()) { 0902 qDebug() << "Error : Expected json array of children while parsing groups"; 0903 continue; 0904 } 0905 // Adjust offset 0906 adjustOffset(updatedNodes, obj, offset, trackMap, ratio); 0907 QJsonObject currentGroup; 0908 currentGroup.insert(QLatin1String("children"), QJsonValue(updatedNodes)); 0909 currentGroup.insert(QLatin1String("type"), QJsonValue(groupTypeToStr(type))); 0910 newGroups.append(QJsonValue(currentGroup)); 0911 } 0912 0913 // Group 0914 for (const auto &elem : newGroups) { 0915 if (!elem.isObject()) { 0916 qDebug() << "Error : Expected json object while parsing groups"; 0917 break; 0918 } 0919 ok = ok && fromJson(elem.toObject(), local_undo, local_redo); 0920 } 0921 0922 if (ok) { 0923 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 0924 } else { 0925 bool undone = local_undo(); 0926 Q_ASSERT(undone); 0927 } 0928 return ok; 0929 } 0930 0931 void GroupsModel::setType(int gid, GroupType type) 0932 { 0933 Q_ASSERT(m_groupIds.count(gid) != 0); 0934 if (type == GroupType::Leaf) { 0935 Q_ASSERT(m_downLink[gid].size() == 0); 0936 if (m_groupIds.count(gid) > 0) { 0937 m_groupIds.erase(gid); 0938 } 0939 } else { 0940 m_groupIds[gid] = type; 0941 } 0942 } 0943 0944 bool GroupsModel::checkConsistency(bool failOnSingleGroups, bool checkTimelineConsistency) 0945 { 0946 // check that all element with up link have a down link 0947 for (const auto &elem : m_upLink) { 0948 if (m_downLink.count(elem.first) == 0) { 0949 qDebug() << "ERROR: Group model has missing up/down links"; 0950 return false; 0951 } 0952 } 0953 // check that all element with down link have a up link 0954 for (const auto &elem : m_downLink) { 0955 if (m_upLink.count(elem.first) == 0) { 0956 qDebug() << "ERROR: Group model has missing up/down links"; 0957 return false; 0958 } 0959 } 0960 0961 int selectionCount = 0; 0962 for (const auto &elem : m_upLink) { 0963 // iterate through children to check links 0964 for (const auto &child : m_downLink[elem.first]) { 0965 if (m_upLink[child] != elem.first) { 0966 qDebug() << "ERROR: Group model has inconsistent up/down links"; 0967 return false; 0968 } 0969 } 0970 bool isLeaf = m_downLink[elem.first].empty(); 0971 if (isLeaf) { 0972 if (m_groupIds.count(elem.first) > 0) { 0973 qDebug() << "ERROR: Group model has wrong tracking of non-leaf groups"; 0974 return false; 0975 } 0976 } else { 0977 if (m_groupIds.count(elem.first) == 0) { 0978 qDebug() << "ERROR: Group model has wrong tracking of non-leaf groups"; 0979 return false; 0980 } 0981 if (m_downLink[elem.first].size() == 1 && failOnSingleGroups) { 0982 qDebug() << "ERROR: Group model contains groups with single element"; 0983 return false; 0984 } 0985 if (getType(elem.first) == GroupType::Selection) { 0986 selectionCount++; 0987 } 0988 if (elem.second != -1 && getType(elem.first) == GroupType::Selection) { 0989 qDebug() << "ERROR: Group model contains inner groups of selection type"; 0990 return false; 0991 } 0992 if (getType(elem.first) == GroupType::Leaf) { 0993 qDebug() << "ERROR: Group model contains groups of Leaf type"; 0994 return false; 0995 } 0996 } 0997 } 0998 if (selectionCount > 1) { 0999 qDebug() << "ERROR: Found too many selections: " << selectionCount; 1000 return false; 1001 } 1002 1003 // Finally, we do a depth first visit of the tree to check for loops 1004 std::unordered_set<int> visited; 1005 for (const auto &elem : m_upLink) { 1006 if (elem.second == -1) { 1007 // this is a root, traverse the tree from here 1008 std::stack<int> stack; 1009 stack.push(elem.first); 1010 while (!stack.empty()) { 1011 int cur = stack.top(); 1012 stack.pop(); 1013 if (visited.count(cur) > 0) { 1014 qDebug() << "ERROR: Group model contains a cycle"; 1015 return false; 1016 } 1017 visited.insert(cur); 1018 for (int child : m_downLink[cur]) { 1019 stack.push(child); 1020 } 1021 } 1022 } 1023 } 1024 1025 // Do a last pass to check everybody was visited 1026 for (const auto &elem : m_upLink) { 1027 if (visited.count(elem.first) == 0) { 1028 qDebug() << "ERROR: Group model contains unreachable elements"; 1029 return false; 1030 } 1031 } 1032 1033 if (checkTimelineConsistency) { 1034 if (auto ptr = m_parent.lock()) { 1035 auto isTimelineObject = [&](int cid) { return ptr->isClip(cid) || ptr->isComposition(cid); }; 1036 for (int g : ptr->m_allGroups) { 1037 if (m_upLink.count(g) == 0 || getType(g) == GroupType::Leaf) { 1038 qDebug() << "ERROR: Timeline contains inconsistent group data"; 1039 return false; 1040 } 1041 } 1042 for (const auto &elem : m_upLink) { 1043 if (getType(elem.first) == GroupType::Leaf) { 1044 if (!isTimelineObject(elem.first)) { 1045 qDebug() << "ERROR: Group model contains leaf element that is not a clip nor a composition"; 1046 return false; 1047 } 1048 } else { 1049 if (ptr->m_allGroups.count(elem.first) == 0) { 1050 qDebug() << "ERROR: Group model contains group element that is not registered on timeline"; 1051 Q_ASSERT(false); 1052 return false; 1053 } 1054 if (getType(elem.first) == GroupType::AVSplit) { 1055 if (m_downLink[elem.first].size() != 2) { 1056 qDebug() << "ERROR: Group model contains a AVSplit group with a children count != 2"; 1057 return false; 1058 } 1059 auto it = m_downLink[elem.first].begin(); 1060 int cid1 = (*it); 1061 ++it; 1062 int cid2 = (*it); 1063 if (!isTimelineObject(cid1) || !isTimelineObject(cid2)) { 1064 qDebug() << "ERROR: Group model contains an AVSplit group with invalid members"; 1065 return false; 1066 } 1067 int tid1 = ptr->getClipTrackId(cid1); 1068 bool isAudio1 = ptr->getTrackById(tid1)->isAudioTrack(); 1069 int tid2 = ptr->getClipTrackId(cid2); 1070 bool isAudio2 = ptr->getTrackById(tid2)->isAudioTrack(); 1071 if (isAudio1 == isAudio2) { 1072 qDebug() << "ERROR: Group model contains an AVSplit formed with members that are both on an audio track or on a video track"; 1073 return false; 1074 } 1075 } 1076 } 1077 } 1078 } 1079 } 1080 return true; 1081 }