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