timelinemodel.hpp 15.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/***************************************************************************
 *   Copyright (C) 2017 by Nicolas Carion                                  *
 *   This file is part of Kdenlive. See www.kdenlive.org.                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) version 3 or any later version accepted by the       *
 *   membership of KDE e.V. (or its successor approved  by the membership  *
 *   of KDE e.V.), which shall act as a proxy defined in Section 14 of     *
 *   version 3 of the license.                                             *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

22 23 24
#ifndef TIMELINEMODEL_H
#define TIMELINEMODEL_H

25
#include <memory>
26
#include <unordered_map>
27
#include <unordered_set>
28
#include <mlt++/MltTractor.h>
29
#include "undohelper.hpp"
30

31
class TrackModel;
32
class ClipModel;
33
class GroupsModel;
34
class DocUndoStack;
35
class SnapModel;
36 37

/* @brief This class represents a Timeline object, as viewed by the backend.
38 39 40 41
   In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the validity of the modifications.

   This class also serves to keep track of all objects. It holds pointers to all tracks and clips, and gives them unique IDs on creation. These Ids are used in any interactions with the objects and have nothing to do with Melt IDs.

42
   This is the entry point for any modifications that has to be made on an element. The dataflow beyond this entry point may vary, for example when the user request a clip resize, the call is deferred to the clip itself, that check if there is enough data to extend by the requested amount, compute the new in and out, and then asks the track if there is enough room for extension. To avoid any confusion on which function to call first, rembember to always call the version in timeline. This is also required to generate the Undo/Redo operators
43

Nicolas Carion's avatar
Nicolas Carion committed
44 45 46 47 48
   Generally speaking, we don't check ahead of time if an action is going to succeed or not before applying it.
   We just apply it naively, and if it fails at some point, we use the undo operator that we are constructing on the fly to revert what we have done so far.
   For example, when we move a group of clips, we apply the move operation to all the clips inside this group (in the right order). If none fails, we are good, otherwise we revert what we've already done.
   This kind of behaviour frees us from the burden of simulating the actions before actually applying theme. This is a good thing because this simulation step would be very sensitive to corruptions and small discrepancies, which we try to avoid at all cost.

49

50
   It derives from AbstractItemModel (indirectly through TimelineItemModel) to provide the model to the QML interface. An itemModel is organized with row and columns that contain the data. It can be hierarchical, meaning that a given index (row,column) can contain another level of rows and column.
51
   Our organization is as follows: at the top level, each row contains a track. These rows are in the same order as in the actual timeline.
Nicolas Carion's avatar
Nicolas Carion committed
52 53 54 55 56
   Then each of this row contains itself sub-rows that correspond to the clips.
   Here the order of these sub-rows is unrelated to the chronological order of the clips,
   but correspond to their Id order. For example, if you have three clips, with ids 12, 45 and 150, they will receive row index 0,1 and 2.
   This is because the order actually doesn't matter since the clips are rendered based on their positions rather than their row order.
   The id order has been choosed because it is consistant with a valid ordering of the clips.
57 58 59 60 61
   The columns are never used, so the data is always in column 0

   An ModelIndex in the ItemModel consists of a row number, a column number, and a parent index. In our case, tracks have always an empty parent, and the clip have a track index as parent.
   A ModelIndex can also store one additional integer, and we exploit this feature to store the unique ID of the object it corresponds to. 

62
*/
63
class TimelineModel : public std::enable_shared_from_this<TimelineModel>
64
{
65 66 67 68

protected:
    /* @brief this constructor should not be called. Call the static construct instead
     */
69
    TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack);
70

71
public:
72
    friend class TrackModel;
73
    friend class ClipModel;
74
    friend class GroupsModel;
75

76
    virtual ~TimelineModel();
77
    Mlt::Tractor* tractor() const { return m_tractor.get(); }
78

79
    /* @brief returns the number of tracks */
80
    int getTracksCount() const;
81

82
    /* @brief returns the number of clips */
83
    int getClipsCount() const;
84

85 86 87 88 89 90 91 92 93 94
    /* @brief Returns the id of the track containing clip (-1 if it is not inserted)
       @param cid Id of the clip to test
     */
    int getClipTrackId(int cid) const;

    /* @brief Returns the position of clip (-1 if it is not inserted)
       @param cid Id of the clip to test
    */
    int getClipPosition(int cid) const;

95 96 97 98 99
    /* @brief Returns the duration of a clip
       @param cid Id of the clip to test
    */
    int getClipPlaytime(int cid) const;

100 101 102 103 104
    /* @brief Returns the number of clips in a given track
       @param tid Id of the track to test
    */
    int getTrackClipsCount(int tid) const;

105 106 107 108 109
    /* @brief Returns the position of the track in the order of the tracks
       @param tid Id of the track to test
    */
    int getTrackPosition(int tid) const;

110
    /* @brief Move a clip to a specific position
111
       This action is undoable
112
       Returns true on success. If it fails, nothing is modified.
113
       If the clip is not in inserted in a track yet, it gets inserted for the first time.
114
       If the clip is in a group, the call is deferred to requestGroupMove
115 116
       @param cid is the ID of the clip
       @param tid is the ID of the target track
117
       @param position is the position where we want to move
118 119
       @param updateView if set to false, no signal is sent to qml
       @param logUndo if set to false, no undo object is stored
120
    */
121
    bool requestClipMove(int cid, int tid, int position, bool updateView = true, bool logUndo = true);
122
    Mlt::Profile *profile();
123

124 125
protected:
    /* Same function, but accumulates undo and redo, and doesn't check for group*/
126
    bool requestClipMove(int cid, int tid, int position, bool updateView, Fun &undo, Fun &redo);
127
public:
128

129 130 131 132 133 134
    /* @brief Given an intended move, try to suggest a more valid one
       @param cid id of the clip to move
       @param tid id of the target track
       @param position target position of the clip
     */
    int suggestClipMove(int cid, int tid, int position);
135 136 137 138 139 140 141 142 143

    /* @brief Request clip insertion at given position.
       This action is undoable
       Returns true on success. If it fails, nothing is modified.
       @param prod Producer of the element to insert
       @param track Id of the track where to insert
       @param Requested position
       @param ID return parameter of the id of the inserted clip
    */
144
    bool requestClipInsertion(std::shared_ptr<Mlt::Producer> prod, int trackId, int position, int &id);
145

146
    /* @brief Deletes the given clip from the timeline
147
       This action is undoable
148 149 150 151 152 153 154 155
       Returns true on success. If it fails, nothing is modified.
       If the clip is in a group, the call is deferred to requestGroupDeletion
       @param cid is the ID of the clip
    */
    bool requestClipDeletion(int cid);
    /* Same function, but accumulates undo and redo, and doesn't check for group*/
    bool requestClipDeletion(int cid, Fun &undo, Fun &redo);

156
    /* @brief Move a group to a specific position
157
       This action is undoable
158 159
       Returns true on success. If it fails, nothing is modified.
       If the clips in the group are not in inserted in a track yet, they get inserted for the first time.
160 161
       @param cid is the id of the clip that triggers the group move
       @param gid is the id of the group
162
       @param delta_track is the delta applied to the track index
163 164
       @param delta_pos is the requested position change
       @param updateView if set to false, no signal is sent to qml for the clip cid
165
       @param logUndo if set to true, an undo object is created
166
    */
167
    bool requestGroupMove(int cid, int gid, int delta_track, int delta_pos, bool updateView = true, bool logUndo = true);
168

169
    /* @brief Deletes all clips inside the group that contains the given clip.
170
       This action is undoable
171 172 173 174 175 176
       Note that if their is a hierarchy of groups, all of them will be deleted.
       Returns true on success. If it fails, nothing is modified.
       @param cid is the id of the clip that triggers the group deletion
    */
    bool requestGroupDeletion(int cid);

177
    /* @brief Change the duration of a clip
178
       This action is undoable
179 180 181 182
       Returns true on success. If it fails, nothing is modified.
       @param cid is the ID of the clip
       @param size is the new size of the clip
       @param right is true if we change the right side of the clip, false otherwise
183
       @param logUndo if set to true, an undo object is created
184
       @param snap if set to true, the resize order will be coerced to use the snapping grid
185
    */
186
    bool requestClipResize(int cid, int size, bool right, bool logUndo = true, bool snap = false);
187 188

    /* @brief Similar to requestClipResize but takes a delta instead of absolute size
189
       This action is undoable
190 191 192 193 194 195 196 197
       Returns true on success. If it fails, nothing is modified.
       @param cid is the ID of the clip
       @param delta is the delta to be applied to the length of the clip
       @param right is true if we change the right side of the clip, false otherwise
       @param ripple TODO document this
       @param test_only if set to true, the undo is not created and no signal is sent to qml
     */
    bool requestClipTrim(int cid, int delta, bool right, bool ripple = false, bool test_only = false);
198

199
    /* @brief Group together a set of ids
200
       The ids are either a group ids or clip ids. The involved clip must already be inserted in a track
201
       This action is undoable
202
       Returns true on success. If it fails, nothing is modified.
203 204 205
       Typically, ids would be ids of clips, but for convenience, some of them can be ids of groups as well.
       @param ids Set of ids to group
    */
206
    bool requestClipsGroup(const std::unordered_set<int>& ids);
207 208

    /* @brief Destruct the topmost group containing clip
209
       This action is undoable
210
       Returns true on success. If it fails, nothing is modified.
211 212
       @param id of the clip to degroup (all clips belonging to the same group will be ungrouped as well)
    */
213
    bool requestClipUngroup(int id);
214 215
    /* Same function, but accumulates undo and redo*/
    bool requestClipUngroup(int id, Fun& undo, Fun& redo);
216

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    /* @brief Create a track at given position
       This action is undoable
       Returns true on success. If it fails, nothing is modified.
       @param Requested position (order). If set to -1, the track is inserted last.
       @param id is a return parameter that holds the id of the resulting track (-1 on failure)
    */
    bool requestTrackInsertion(int pos, int& id);

    /* @brief Delete track with given id
       This also deletes all the clips contained in the track.
       This action is undoable
       Returns true on success. If it fails, nothing is modified.
       @param tid id of the track to delete
    */
    bool requestTrackDeletion(int tid);

233 234 235 236 237
    /* @brief Get project duration
       Returns the duration in frames
    */
    int duration() const;

238 239 240 241 242 243
    /* @brief Get all the elements of the same group as the given clip.
       If there is a group hierarchy, only the topmost group is considered.
       @param cid id of the clip to test
    */
    std::unordered_set<int> getGroupElements(int cid);

244 245 246 247
protected:
    /* @brief Register a new track. This is a call-back meant to be called from TrackModel
       @param pos indicates the number of the track we are adding. If this is -1, then we add at the end.
     */
248
    void registerTrack(std::shared_ptr<TrackModel> track, int pos = -1);
249

250 251 252 253
    /* @brief Register a new track. This is a call-back meant to be called from ClipModel
    */
    void registerClip(std::shared_ptr<ClipModel> clip);

254 255 256 257
    /* @brief Register a new group. This is a call-back meant to be called from GroupsModel
     */
    void registerGroup(int groupId);

258 259
    /* @brief Deregister and destruct the track with given id.
     */
260
    Fun deregisterTrack_lambda(int id);
261

262 263
    /* @brief Return a lambda that deregisters and destructs the clip with given id.
       Note that the clip must already be deleted from its track and groups.
264
     */
265
    Fun deregisterClip_lambda(int id);
266

267 268 269 270
    /* @brief Deregister a group with given id
     */
    void deregisterGroup(int id);

271 272
    /* @brief Helper function to get a pointer to the track, given its id
     */
273 274
    std::shared_ptr<TrackModel> getTrackById(int tid);
    const std::shared_ptr<TrackModel> getTrackById_const(int tid) const;
275

276 277 278
    /*@brief Helper function to get a pointer to a clip, given its id*/
    std::shared_ptr<ClipModel> getClipPtr(int cid) const;

279 280 281
    /* @brief Returns next valid unique id to create an object
     */
    static int getNextId();
282

283
    /* @brief Helper function that returns true if the given ID corresponds to a clip
284 285 286
     */
    bool isClip(int id) const;

287
    /* @brief Helper function that returns true if the given ID corresponds to a track
288 289
     */
    bool isTrack(int id) const;
290 291 292 293

    /* @brief Helper function that returns true if the given ID corresponds to a track
     */
    bool isGroup(int id) const;
294
protected:
295
    std::unique_ptr<Mlt::Tractor> m_tractor;
296

297
    std::list<std::shared_ptr<TrackModel>> m_allTracks;
298

299
    std::unordered_map<int, std::list<std::shared_ptr<TrackModel>>::iterator> m_iteratorTable; //this logs the iterator associated which each track id. This allows easy access of a track based on its id.
300

301
    std::unordered_map<int, std::shared_ptr<ClipModel>> m_allClips; //the keys are the clip id, and the values are the corresponding pointers
302 303

    static int next_id;//next valid id to assign
304 305

    std::unique_ptr<GroupsModel> m_groups;
306
    std::unique_ptr<SnapModel> m_snaps;
307 308 309

    std::unordered_set<int> m_allGroups; //ids of all the groups

310 311
    std::weak_ptr<DocUndoStack> m_undoStack;

312 313 314
private:
    // The black track producer. It's length / out should always be adjusted to the projects's length
    Mlt::Producer *m_blackClip;
315 316 317 318 319 320 321 322 323 324

    //what follows are some virtual function that corresponds to the QML. They are implemented in TimelineItemModel
protected:
    virtual void _beginRemoveRows(const QModelIndex&, int , int) = 0;
    virtual void _beginInsertRows(const QModelIndex&, int , int) = 0;
    virtual void _endRemoveRows() = 0;
    virtual void _endInsertRows() = 0;
    virtual void notifyChange(const QModelIndex& topleft, const QModelIndex& bottomright, bool start, bool duration) = 0;
    virtual QModelIndex makeClipIndexFromID(int) const = 0;
    virtual QModelIndex makeTrackIndexFromID(int) const = 0;
325
};
326
#endif
327