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 };