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 }