1 /* 2 SPDX-FileCopyrightText: 2017 Nicolas Carion 3 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 4 */ 5 6 #ifndef GROUPMODEL_H 7 #define GROUPMODEL_H 8 9 #include "definitions.h" 10 #include "undohelper.hpp" 11 #include <QReadWriteLock> 12 #include <memory> 13 #include <unordered_map> 14 #include <unordered_set> 15 16 class TimelineItemModel; 17 18 /** @class GroupsModel 19 @brief This class represents the group hierarchy. This is basically a tree structure 20 In this class, we consider that a groupItem is either a clip or a group 21 */ 22 class GroupsModel 23 { 24 25 public: 26 GroupsModel() = delete; 27 GroupsModel(std::weak_ptr<TimelineItemModel> parent); 28 29 /** @brief Create a group that contains all the given items and returns the id of the created group. 30 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. 31 If only one id is provided, no group is created, unless force = true. 32 @param ids set containing the items to group. 33 @param undo Lambda function containing the current undo stack. Will be updated with current operation 34 @param redo Lambda function containing the current redo queue. Will be updated with current operation 35 @param type indicates the type of group we create 36 Returns the id of the new group, or -1 on error. 37 */ 38 int groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal, bool force = false); 39 40 protected: 41 /* Lambda version */ 42 Fun groupItems_lambda(int gid, const std::unordered_set<int> &ids, GroupType type = GroupType::Normal, int parent = -1); 43 44 public: 45 /** @brief Deletes the topmost group containing given element 46 Note that if the element is not in a group, then it will not be touched. 47 Return true on success 48 @param id id of the groupitem 49 @param undo Lambda function containing the current undo stack. Will be updated with current operation 50 @param redo Lambda function containing the current redo queue. Will be updated with current operation 51 */ 52 bool ungroupItem(int id, Fun &undo, Fun &redo, bool deleteOrphan = true); 53 54 /** @brief Create a groupItem in the hierarchy. Initially it is not part of a group 55 @param id id of the groupItem 56 */ 57 void createGroupItem(int id); 58 59 /** @brief Destruct a group item 60 Note that this public function expects that the given id is an orphan element. 61 @param id id of the groupItem 62 */ 63 bool destructGroupItem(int id); 64 65 /** @brief Merges group with only one child to parent 66 Ex: . . 67 / \ / \ 68 . . becomes a b 69 / \ 70 a b 71 @param id id of the tree to consider 72 */ 73 bool mergeSingleGroups(int id, Fun &undo, Fun &redo); 74 75 /** @brief Split the group tree according to a given criterion 76 All the leaves satisfying the criterion are moved to the new tree, the other stay 77 Both tree are subsequently simplified to avoid weird structure. 78 @param id is the root of the tree 79 */ 80 bool split(int id, const std::function<bool(int)> &criterion, Fun &undo, Fun &redo); 81 82 /** @brief Copy a group hierarchy. 83 @param mapping describes the correspondence between the ids of the items in the source group hierarchy, 84 and their counterpart in the hierarchy that we create. 85 It will also be used as a return parameter, by adding the mapping between the groups of the hierarchy 86 Note that if the target items should not belong to a group. 87 */ 88 bool copyGroups(std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo); 89 90 /** @brief Get the overall father of a given groupItem 91 If the element has no father, it is returned as is. 92 @param id id of the groupitem 93 */ 94 int getRootId(int id) const; 95 96 /** @brief Returns true if the groupItem has no descendant 97 @param id of the groupItem 98 */ 99 bool isLeaf(int id) const; 100 101 /** @brief Returns true if the element is in a non-trivial group 102 @param id of the groupItem 103 */ 104 bool isInGroup(int id) const; 105 106 /** @brief Move element id in the same group as targetId */ 107 void setInGroupOf(int id, int targetId, Fun &undo, Fun &redo); 108 109 /** @brief We replace the leaf node given by id with a group that contains the leaf plus all the clips in to_add. 110 * The created group type is given in parameter 111 * Returns true on success 112 */ 113 bool createGroupAtSameLevel(int id, std::unordered_set<int> to_add, GroupType type, Fun &undo, Fun &redo); 114 115 /** @brief Returns the id of all the descendant of given item (including item) 116 @param id of the groupItem 117 */ 118 std::unordered_set<int> getSubtree(int id) const; 119 120 /** @brief Returns the id of all the leaves in the subtree of the given item 121 This should correspond to the ids of the clips, since they should be the only items with no descendants 122 @param id of the groupItem 123 */ 124 std::unordered_set<int> getLeaves(int id) const; 125 126 /** @brief Gets direct children of a given group item 127 @param id of the groupItem 128 */ 129 std::unordered_set<int> getDirectChildren(int id) const; 130 131 /** @brief Gets direct ancestor of a given group item. Returns -1 if not in a group 132 @param id of the groupItem 133 */ 134 int getDirectAncestor(int id) const; 135 136 /** @brief Get the type of the group 137 @param id of the groupItem. Must be a proper group, not a leaf 138 */ 139 GroupType getType(int id) const; 140 141 /** @brief Convert the group hierarchy to json. 142 Note that we cannot expect clipId nor groupId to be the same on project reopening, thus we cannot rely on them for saving. 143 To workaround that, we currently identify clips by their position + track 144 */ 145 const QString toJson() const; 146 const QString toJson(std::unordered_set<int> roots) const; 147 bool fromJson(const QString &data); 148 bool fromJsonWithOffset(const QString &data, const QMap<int, int> &trackMap, int offset, Fun &undo, Fun &redo); 149 150 /** @brief if the clip belongs to a AVSplit group, then return the id of the other corresponding clip. Otherwise, returns -1 */ 151 int getSplitPartner(int id) const; 152 153 /** @brief Check the internal consistency of the model. Returns false if something is wrong 154 @param failOnSingleGroups: if true, we make sure that a non-leaf node has at least two children 155 @param checkTimelineConsistency: if true, we make sure that the group data of the parent timeline are consistent 156 */ 157 bool checkConsistency(bool failOnSingleGroups = true, bool checkTimelineConsistency = false); 158 159 /** @brief Remove an item from all the groups it belongs to. 160 @param id of the groupItem 161 */ 162 void removeFromGroup(int id); 163 164 /** @brief change the group of a given item 165 @param id of the groupItem 166 @param groupId id of the group to assign it to 167 @param changeState when false, the grouped role for item won't be updated (for selection) 168 */ 169 void setGroup(int id, int groupId, bool changeState = true); 170 171 QString debugString(); 172 173 protected: 174 /** @brief Destruct a groupItem in the hierarchy. 175 All its children will become their own roots 176 Return true on success 177 @param id id of the groupitem 178 @param deleteOrphan If this parameter is true, we recursively delete any group that become empty following the destruction 179 @param undo Lambda function containing the current undo stack. Will be updated with current operation 180 @param redo Lambda function containing the current redo queue. Will be updated with current operation 181 */ 182 bool destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo); 183 /* Lambda version */ 184 Fun destructGroupItem_lambda(int id); 185 186 /** @brief This is the actual recursive implementation of the copy function. */ 187 bool processCopy(int gid, std::unordered_map<int, int> &mapping, Fun &undo, Fun &redo); 188 189 /** @brief This is the actual recursive implementation of the conversion to json */ 190 QJsonObject toJson(int gid) const; 191 192 /** @brief This is the actual recursive implementation of the parsing from json 193 Returns the id of the created group 194 */ 195 int fromJson(const QJsonObject &o, Fun &undo, Fun &redo); 196 197 /** @brief Transform a leaf node into a group node of given type. This implies doing the registration to the timeline */ 198 void promoteToGroup(int gid, GroupType type); 199 200 /** @brief Transform a group node with no children into a leaf. This implies doing the deregistration to the timeline */ 201 void downgradeToLeaf(int gid); 202 203 /** @brief helper function to change the type of a group. 204 @param id of the groupItem 205 @param type: new type of the group 206 */ 207 void setType(int gid, GroupType type); 208 209 void adjustOffset(QJsonArray &updatedNodes, QJsonObject childObject, int offset, const QMap<int, int> &trackMap); 210 211 private: 212 std::weak_ptr<TimelineItemModel> m_parent; 213 214 /** @brief edges toward parent */ 215 std::unordered_map<int, int> m_upLink; 216 /** @brief edges toward children */ 217 std::unordered_map<int, std::unordered_set<int>> m_downLink; 218 /** @brief this keeps track of "real" groups (non-leaf elements), and their types */ 219 std::unordered_map<int, GroupType> m_groupIds; 220 /** @brief This is a lock that ensures safety in case of concurrent access */ 221 mutable QReadWriteLock m_lock; 222 }; 223 224 #endif 225