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 #pragma once
0007 
0008 #include "definitions.h"
0009 #include "undohelper.hpp"
0010 #include <QReadWriteLock>
0011 #include <memory>
0012 #include <unordered_map>
0013 #include <unordered_set>
0014 
0015 class TimelineItemModel;
0016 
0017 /** @class GroupsModel
0018     @brief This class represents the group hierarchy. This is basically a tree structure
0019     In this class, we consider that a groupItem is either a clip or a group
0020 */
0021 class GroupsModel
0022 {
0023 
0024 public:
0025     GroupsModel() = delete;
0026     GroupsModel(std::weak_ptr<TimelineItemModel> parent);
0027 
0028     /** @brief Create a group that contains all the given items and returns the id of the created group.
0029        Note that if an item is already part of a group, its topmost group will be considered instead and added in the newly created group.
0030        If only one id is provided, no group is created, unless force = true.
0031        @param ids set containing the items to group.
0032        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0033        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0034        @param type indicates the type of group we create
0035        Returns the id of the new group, or -1 on error.
0036     */
0037     int groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal, bool force = false);
0038 
0039 protected:
0040     /* Lambda version */
0041     Fun groupItems_lambda(int gid, const std::unordered_set<int> &ids, GroupType type = GroupType::Normal, int parent = -1);
0042 
0043 public:
0044     /** @brief Deletes the topmost group containing given element
0045        Note that if the element is not in a group, then it will not be touched.
0046        Return true on success
0047        @param id id of the groupitem
0048        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0049        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0050      */
0051     bool ungroupItem(int id, Fun &undo, Fun &redo, bool deleteOrphan = true);
0052 
0053     /** @brief Create a groupItem in the hierarchy. Initially it is not part of a group
0054        @param id id of the groupItem
0055     */
0056     void createGroupItem(int id);
0057 
0058     /** @brief Destruct a group item
0059        Note that this public function expects that the given id is an orphan element.
0060        @param id id of the groupItem
0061     */
0062     bool destructGroupItem(int id);
0063 
0064     /** @brief Merges group with only one child to parent
0065        Ex:   .                     .
0066             / \                   / \
0067            .   .    becomes      a   b
0068           /     \
0069          a       b
0070        @param id id of the tree to consider
0071      */
0072     bool mergeSingleGroups(int id, Fun &undo, Fun &redo);
0073 
0074     /** @brief Split the group tree according to a given criterion
0075        All the leaves satisfying the criterion are moved to the new tree, the other stay
0076        Both tree are subsequently simplified to avoid weird structure.
0077        @param id is the root of the tree
0078      */
0079     bool split(int id, const std::function<bool(int)> &criterion, Fun &undo, Fun &redo);
0080 
0081     /** @brief Copy a group hierarchy.
0082        @param mapping describes the correspondence between the ids of the items in the source group hierarchy,
0083        and their counterpart in the hierarchy that we create.
0084        It will also be used as a return parameter, by adding the mapping between the groups of the hierarchy
0085        Note that if the target items should not belong to a group.
0086     */
0087     bool copyGroups(std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo);
0088 
0089     /** @brief Get the overall father of a given groupItem
0090        If the element has no father, it is returned as is.
0091        @param id id of the groupitem
0092     */
0093     int getRootId(int id) const;
0094 
0095     /** @brief Returns true if the groupItem has no descendant
0096        @param id of the groupItem
0097     */
0098     bool isLeaf(int id) const;
0099 
0100     /** @brief Returns true if the element is in a non-trivial group
0101        @param id of the groupItem
0102     */
0103     bool isInGroup(int id) const;
0104 
0105     /** @brief Move element id in the same group as targetId */
0106     void setInGroupOf(int id, int targetId, Fun &undo, Fun &redo);
0107 
0108     /** @brief We replace the leaf node given by id with a group that contains the leaf plus all the clips in to_add.
0109      * The created group type is given in parameter
0110      * Returns true on success
0111      */
0112     bool createGroupAtSameLevel(int id, std::unordered_set<int> to_add, GroupType type, Fun &undo, Fun &redo);
0113 
0114     /** @brief Returns the id of all the descendant of given item (including item)
0115        @param id of the groupItem
0116     */
0117     std::unordered_set<int> getSubtree(int id) const;
0118 
0119     /** @brief Returns the id of all the leaves in the subtree of the given item
0120        This should correspond to the ids of the clips, since they should be the only items with no descendants
0121        @param id of the groupItem
0122     */
0123     std::unordered_set<int> getLeaves(int id) const;
0124 
0125     /** @brief Gets direct children of a given group item
0126        @param id of the groupItem
0127      */
0128     std::unordered_set<int> getDirectChildren(int id) const;
0129 
0130     /** @brief Gets direct ancestor of a given group item. Returns -1 if not in a group
0131        @param id of the groupItem
0132     */
0133     int getDirectAncestor(int id) const;
0134 
0135     /** @brief Get the type of the group
0136        @param id of the groupItem. Must be a proper group, not a leaf
0137     */
0138     GroupType getType(int id) const;
0139 
0140     /** @brief Convert the group hierarchy to json.
0141        Note that we cannot expect clipId nor groupId to be the same on project reopening, thus we cannot rely on them for saving.
0142        To workaround that, we currently identify clips by their position + track
0143     */
0144     const QString toJson() const;
0145     const QString toJson(const std::unordered_set<int> &roots) const;
0146     bool fromJson(const QString &data);
0147     bool fromJsonWithOffset(const QString &data, const QMap<int, int> &trackMap, int offset, double ratio, Fun &undo, Fun &redo);
0148 
0149     /** @brief if the clip belongs to a AVSplit group, then return the id of the other corresponding clip. Otherwise, returns -1 */
0150     int getSplitPartner(int id) const;
0151 
0152     /** @brief Check the internal consistency of the model. Returns false if something is wrong
0153        @param failOnSingleGroups: if true, we make sure that a non-leaf node has at least two children
0154        @param checkTimelineConsistency: if true, we make sure that the group data of the parent timeline are consistent
0155     */
0156     bool checkConsistency(bool failOnSingleGroups = true, bool checkTimelineConsistency = false);
0157 
0158     /** @brief Remove an item from all the groups it belongs to.
0159        @param id of the groupItem
0160     */
0161     void removeFromGroup(int id);
0162 
0163     /** @brief change the group of a given item
0164        @param id of the groupItem
0165        @param groupId id of the group to assign it to
0166        @param changeState when false, the grouped role for item won't be updated (for selection)
0167     */
0168     void setGroup(int id, int groupId, bool changeState = true);
0169 
0170     QString debugString();
0171 
0172 protected:
0173     /** @brief Destruct a groupItem in the hierarchy.
0174        All its children will become their own roots
0175        Return true on success
0176        @param id id of the groupitem
0177        @param deleteOrphan If this parameter is true, we recursively delete any group that become empty following the destruction
0178        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0179        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0180     */
0181     bool destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo);
0182     /* Lambda version */
0183     Fun destructGroupItem_lambda(int id);
0184 
0185     /** @brief This is the actual recursive implementation of the copy function. */
0186     bool processCopy(int gid, std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo);
0187 
0188     /** @brief This is the actual recursive implementation of the conversion to json */
0189     QJsonObject toJson(int gid) const;
0190 
0191     /** @brief This is the actual recursive implementation of the parsing from json
0192        Returns the id of the created group
0193     */
0194     int fromJson(const QJsonObject &o, Fun &undo, Fun &redo);
0195 
0196     /** @brief Transform a leaf node into a group node of given type. This implies doing the registration to the timeline */
0197     void promoteToGroup(int gid, GroupType type);
0198 
0199     /** @brief Transform a group node with no children into a leaf. This implies doing the deregistration to the timeline */
0200     void downgradeToLeaf(int gid);
0201 
0202     /** @brief helper function to change the type of a group.
0203        @param id of the groupItem
0204        @param type: new type of the group
0205     */
0206     void setType(int gid, GroupType type);
0207 
0208     /** @brief Adjust json group data according to offset
0209        @param updatedNodes The resulting nodes
0210        @param childObject The source data
0211        @param offset The position frame offset
0212        @param trackMap The map from source to destination tracks
0213        @param ratio A ratio to apply to all positions (used in case of fps conversion)
0214     */
0215     void adjustOffset(QJsonArray &updatedNodes, const QJsonObject &childObject, int offset, const QMap<int, int> &trackMap, double ratio = 1.);
0216 
0217 private:
0218     std::weak_ptr<TimelineItemModel> m_parent;
0219 
0220     /** @brief edges toward parent */
0221     std::unordered_map<int, int> m_upLink;
0222     /** @brief edges toward children */
0223     std::unordered_map<int, std::unordered_set<int>> m_downLink;
0224     /** @brief this keeps track of "real" groups (non-leaf elements), and their types */
0225     std::unordered_map<int, GroupType> m_groupIds;
0226     /** @brief This is a lock that ensures safety in case of concurrent access */
0227     mutable QReadWriteLock m_lock;
0228 };