timelinemodel.hpp 39.3 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 "definitions.h"
Nicolas Carion's avatar
Nicolas Carion committed
26 27 28
#include "undohelper.hpp"
#include <QAbstractItemModel>
#include <QReadWriteLock>
Nicolas Carion's avatar
Nicolas Carion committed
29
#include <cassert>
30
#include <memory>
Nicolas Carion's avatar
Nicolas Carion committed
31
#include <mlt++/MltTractor.h>
32
#include <unordered_map>
33
#include <unordered_set>
Nicolas Carion's avatar
Nicolas Carion committed
34
#include <vector>
35

36
class AssetParameterModel;
37
class EffectStackModel;
38
class ClipModel;
39
class CompositionModel;
40
class DocUndoStack;
41
class GroupsModel;
42
class SnapModel;
43
class TimelineItemModel;
44
class TrackModel;
45 46

/* @brief This class represents a Timeline object, as viewed by the backend.
Nicolas Carion's avatar
Nicolas Carion committed
47 48
   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.
49

Nicolas Carion's avatar
Nicolas Carion committed
50 51
   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.
52

Nicolas Carion's avatar
Nicolas Carion committed
53 54 55 56
   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
57

Nicolas Carion's avatar
Nicolas Carion committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71
   The undo/redo system is designed around lambda functions. Each time a function executes an elementary change to the model, it writes the corresponding
   operation and its reverse, respectively in the redo and the undo lambdas. This way, if an operation fails for some reason, we can easily cancel the steps
   that have been done so far without corrupting anything. The other advantage is that operations are easy to compose, and you get a undo/redo pair for free no
   matter in which way you combine them.

   Most of the modification functions are named requestObjectAction. Eg, if the object is a clip and we want to move it, we call requestClipMove. These
   functions always return a bool indicating success, and when they return false they should guarantee than nothing has been modified. Most of the time, these
   functions come in two versions: the first one is the entry point if you want to perform only the action (and not compose it with other actions). This version
   will generally automatically push and Undo object on the Application stack, in case the user later wants to cancel the operation. It also generally goes the
   extra mile to ensure the operation is done in a way that match the user's expectation: for example requestClipMove checks whether the clip belongs to a group
   and in that case actually mouves the full group. The other version of the function, if it exists, is intended for composition (using the action as part of a
   complex operation). It takes as input the undo/redo lambda corresponding to the action that is being performed and accumulates on them. Note that this
   version does the minimal job: in the example of the requestClipMove, it will not move the full group if the clip is in a group.

72 73
   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.
Nicolas Carion's avatar
Nicolas Carion committed
74 75 76 77
   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.
78

79

Nicolas Carion's avatar
Nicolas Carion committed
80 81
   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.
82
   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
83 84 85 86
   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.
87
   The id order has been chosen because it is consistent with a valid ordering of the clips.
88 89
   The columns are never used, so the data is always in column 0

Nicolas Carion's avatar
Nicolas Carion committed
90 91 92
   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.
93

94
*/
95
class TimelineModel : public QAbstractItemModel_shared_from_this<TimelineModel>
96
{
97
    Q_OBJECT
98 99 100 101

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

104
public:
105
    friend class TrackModel;
Nicolas Carion's avatar
Nicolas Carion committed
106
    template <typename T> friend class MoveableItem;
107
    friend class ClipModel;
108
    friend class CompositionModel;
109
    friend class GroupsModel;
110
    friend class TimelineController;
Nicolas Carion's avatar
Nicolas Carion committed
111
    friend struct TimelineFunctions;
112

113 114 115
    /// Two level model: tracks and clips on track
    enum {
        NameRole = Qt::UserRole + 1,
Nicolas Carion's avatar
Nicolas Carion committed
116 117 118 119
        ResourceRole, /// clip only
        ServiceRole,  /// clip only
        StartRole,    /// clip only
        BinIdRole,    /// clip only
120
        TrackIdRole,
121 122
        FakeTrackIdRole,
        FakePositionRole,
123 124 125
        MarkersRole, /// clip only
        StatusRole,  /// clip only
        TypeRole,    /// clip only
126
        KeyframesRole,
127
        DurationRole,
Nicolas Carion's avatar
Nicolas Carion committed
128 129 130 131 132 133 134 135
        InPointRole,    /// clip only
        OutPointRole,   /// clip only
        FramerateRole,  /// clip only
        GroupedRole,    /// clip only
        HasAudio,       /// clip only
        CanBeAudioRole, /// clip only
        CanBeVideoRole, /// clip only
        IsDisabledRole, /// track only
136
        IsAudioRole,
137
        SortRole,
138
        ShowKeyframesRole,
139 140 141 142 143 144 145 146 147 148 149 150 151
        AudioLevelsRole,    /// clip only
        AudioChannelsRole,  /// clip only
        IsCompositeRole,    /// track only
        IsLockedRole,       /// track only
        HeightRole,         /// track only
        TrackTagRole,       /// track only
        FadeInRole,         /// clip only
        FadeOutRole,        /// clip only
        FileHashRole,       /// clip only
        SpeedRole,          /// clip only
        ReloadThumbRole,    /// clip only
        PositionOffsetRole, /// clip only
        ItemATrack,         /// composition only
152
        ItemIdRole,
153 154 155
        ThumbsFormatRole,   /// track only
        EffectNamesRole,    // track and clip only
        EffectsEnabledRole, // track and clip only
Nicolas Carion's avatar
Nicolas Carion committed
156
        GrabbedRole,        /// clip+composition only
157
        TrackActiveRole,    /// track only
158
        AudioRecordRole     /// track only
159 160
    };

Nicolas Carion's avatar
Nicolas Carion committed
161
    ~TimelineModel() override;
Nicolas Carion's avatar
Nicolas Carion committed
162
    Mlt::Tractor *tractor() const { return m_tractor.get(); }
163 164 165
    /* @brief Load tracks from the current tractor, used on project opening
     */
    void loadTractor();
166 167 168

    /* @brief Returns the current tractor's producer, useful fo control seeking, playing, etc
     */
169
    std::shared_ptr<Mlt::Producer> producer();
170
    Mlt::Profile *getProfile();
171

172
    /* @brief returns the number of tracks */
173
    int getTracksCount() const;
174

175 176
    /* @brief returns the track index (id) from its position */
    int getTrackIndexFromPosition(int pos) const;
177

178
    /* @brief returns the track index (id) from its position */
179
    Q_INVOKABLE bool isAudioTrack(int trackId) const;
180

181
    /* @brief returns the number of clips */
182
    int getClipsCount() const;
183

184 185 186
    /* @brief returns the number of compositions */
    int getCompositionsCount() const;

187
    /* @brief Returns the id of the track containing clip (-1 if it is not inserted)
188
       @param clipId Id of the clip to test */
189
    Q_INVOKABLE int getClipTrackId(int clipId) const;
190

191 192 193 194 195
    /* @brief Returns the id of the track containing composition (-1 if it is not inserted)
       @param clipId Id of the composition to test */
    Q_INVOKABLE int getCompositionTrackId(int compoId) const;

    /* @brief Convenience function that calls either of the previous ones based on item type*/
196
    Q_INVOKABLE int getItemTrackId(int itemId) const;
197

198 199 200 201
    Q_INVOKABLE int getCompositionPosition(int compoId) const;
    int getCompositionPlaytime(int compoId) const;

    /* Returns an item position, item can be clip or composition */
202
    Q_INVOKABLE int getItemPosition(int itemId) const;
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    /* Returns an item duration, item can be clip or composition */
    int getItemPlaytime(int itemId) const;

    /* Returns the current speed of a clip */
    double getClipSpeed(int clipId) const;

    /* @brief Helper function to query the amount of free space around a clip
     * @param clipId: the queried clip. If it is not inserted on a track, this functions returns 0
     * @param after: if true, we return the blank after the clip, otherwise, before.
     */
    int getBlankSizeNearClip(int clipId, bool after) const;

    /* @brief if the clip belongs to a AVSplit group, then return the id of the other corresponding clip. Otherwise, returns -1 */
    int getClipSplitPartner(int clipId) const;

218
    /* @brief Helper function that returns true if the given ID corresponds to a clip */
219
    Q_INVOKABLE bool isClip(int id) const;
220 221

    /* @brief Helper function that returns true if the given ID corresponds to a composition */
222
    Q_INVOKABLE bool isComposition(int id) const;
223

224 225 226
    /* @brief Helper function that returns true if the given ID corresponds to a timeline item (composition or clip) */
    Q_INVOKABLE bool isItem(int id) const;

227
    /* @brief Helper function that returns true if the given ID corresponds to a track */
228
    Q_INVOKABLE bool isTrack(int id) const;
229

230
    /* @brief Helper function that returns true if the given ID corresponds to a group */
231
    Q_INVOKABLE bool isGroup(int id) const;
232 233 234

    /* @brief Given a composition Id, returns its underlying parameter model */
    std::shared_ptr<AssetParameterModel> getCompositionParameterModel(int compoId) const;
235 236
    /* @brief Given a clip Id, returns its underlying effect stack model */
    std::shared_ptr<EffectStackModel> getClipEffectStackModel(int clipId) const;
237

238
    /* @brief Returns the position of clip (-1 if it is not inserted)
239
       @param clipId Id of the clip to test
240
    */
241
    Q_INVOKABLE int getClipPosition(int clipId) const;
242
    Q_INVOKABLE bool addClipEffect(int clipId, const QString &effectId, bool notify = true);
243
    Q_INVOKABLE bool addTrackEffect(int trackId, const QString &effectId);
244
    bool removeFade(int clipId, bool fromStart);
245
    Q_INVOKABLE bool copyClipEffect(int clipId, const QString &sourceId);
246
    Q_INVOKABLE bool copyTrackEffect(int trackId, const QString &sourceId);
247
    bool adjustEffectLength(int clipId, const QString &effectId, int duration, int initialDuration);
248

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
249 250 251 252
    /* @brief Returns the closest snap point within snapDistance
     */
    Q_INVOKABLE int suggestSnapPoint(int pos, int snapDistance);

253 254 255 256 257
    /** @brief Return the previous track of same type as source trackId, or trackId if no track found */
    Q_INVOKABLE int getPreviousTrackId(int trackId);
    /** @brief Return the next track of same type as source trackId, or trackId if no track found */
    Q_INVOKABLE int getNextTrackId(int trackId);

258 259 260 261 262
    /* @brief Returns the in cut position of a clip
       @param clipId Id of the clip to test
    */
    int getClipIn(int clipId) const;

263
    /* @brief Returns the clip state (audio/video only)
264
     */
265 266
    PlaylistState::ClipState getClipState(int clipId) const;

267 268 269 270 271
    /* @brief Returns the bin id of the clip master
       @param clipId Id of the clip to test
    */
    const QString getClipBinId(int clipId) const;

272
    /* @brief Returns the duration of a clip
273
       @param clipId Id of the clip to test
274
    */
275
    int getClipPlaytime(int clipId) const;
276

277
    /* @brief Returns the size of the clip's frame (widthxheight)
278 279 280
       @param clipId Id of the clip to test
    */
    QSize getClipFrameSize(int clipId) const;
281
    /* @brief Returns the number of clips in a given track
282
       @param trackId Id of the track to test
283
    */
284
    int getTrackClipsCount(int trackId) const;
285 286 287 288

    /* @brief Returns the number of compositions in a given track
       @param trackId Id of the track to test
    */
289
    int getTrackCompositionsCount(int trackId) const;
290

291
    /* @brief Returns the position of the track in the order of the tracks
292
       @param trackId Id of the track to test
293
    */
294
    int getTrackPosition(int trackId) const;
295

296 297 298
    /* @brief Returns the track's index in terms of mlt's internal representation
     */
    int getTrackMltIndex(int trackId) const;
299 300 301 302 303
    /* @brief Returns a sort position for tracks.
     * @param separated: if true, the tracks will be sorted like: V2,V1,A1,A2
     * Otherwise, the tracks will be sorted like V2,A2,V1,A1
     */
    int getTrackSortValue(int trackId, bool separated) const;
304

305 306
    /* @brief Returns the ids of the tracks below the given track in the order of the tracks
       Returns an empty list if no track available
307 308
       @param trackId Id of the track to test
    */
309
    QList<int> getLowerTracksId(int trackId, TrackType type = TrackType::AnyTrack) const;
310

311
    /* @brief Returns the MLT track index of the video track just below the given track
312 313
       @param trackId Id of the track to test
    */
314
    int getPreviousVideoTrackPos(int trackId) const;
315 316 317 318
    /* @brief Returns the Track id of the video track just below the given track
       @param trackId Id of the track to test
    */
    int getPreviousVideoTrackIndex(int trackId) const;
319

320
    /* @brief Returns the Id of the corresponding audio track. If trackId corresponds to video1, this will return audio 1 and so on */
321
    int getMirrorAudioTrackId(int trackId) const;
322
    int getMirrorVideoTrackId(int trackId) const;
323
    int getMirrorTrackId(int trackId) const;
324

325
    /* @brief Move a clip to a specific position
326
       This action is undoable
327
       Returns true on success. If it fails, nothing is modified.
328
       If the clip is not in inserted in a track yet, it gets inserted for the first time.
329
       If the clip is in a group, the call is deferred to requestGroupMove
330 331
       @param clipId is the ID of the clip
       @param trackId is the ID of the target track
332
       @param position is the position where we want to move
333 334
       @param updateView if set to false, no signal is sent to qml
       @param logUndo if set to false, no undo object is stored
335
    */
336
    Q_INVOKABLE bool requestClipMove(int clipId, int trackId, int position, bool updateView = true, bool logUndo = true, bool invalidateTimeline = false);
337

338 339 340 341 342
    /* @brief Move a composition to a specific position This action is undoable
       Returns true on success. If it fails, nothing is modified. If the clip is
       not in inserted in a track yet, it gets inserted for the first time. If
       the clip is in a group, the call is deferred to requestGroupMove @param
       transid is the ID of the composition @param trackId is the ID of the
343 344 345 346 347
       track */
    Q_INVOKABLE bool requestCompositionMove(int compoId, int trackId, int position, bool updateView = true, bool logUndo = true);

    /* Same function, but accumulates undo and redo, and doesn't check
       for group*/
348
    bool requestClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo);
349
    bool requestCompositionMove(int transid, int trackId, int compositionTrack, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo);
350

351
    /* When timeline edit mode is insert or overwrite, we fake the move (as it will overlap existing clips, and only process the real move on drop */
352
    bool requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo);
353
    bool requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool logUndo, bool invalidateTimeline);
354 355
    bool requestFakeGroupMove(int clipId, int groupId, int delta_track, int delta_pos, bool updateView = true, bool logUndo = true);
    bool requestFakeGroupMove(int clipId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo,
356
                              bool allowViewRefresh = true);
357

Nicolas Carion's avatar
Nicolas Carion committed
358
    /* @brief Given an intended move, try to suggest a more valid one
Nicolas Carion's avatar
Nicolas Carion committed
359
       (accounting for snaps and missing UI calls)
360
       @param clipId id of the clip to
Nicolas Carion's avatar
Nicolas Carion committed
361 362
       move
       @param trackId id of the target track
363 364
       @param position target position
       @param snapDistance the maximum distance for a snap result, -1 for no snapping
Nicolas Carion's avatar
Nicolas Carion committed
365
        of the clip
366 367
       @param dontRefreshMasterClip when false, no view refresh is attempted
        */
368
    Q_INVOKABLE int suggestItemMove(int itemId, int trackId, int position, int cursorPosition, int snapDistance = -1);
369
    Q_INVOKABLE int suggestClipMove(int clipId, int trackId, int position, int cursorPosition, int snapDistance = -1);
370
    Q_INVOKABLE int suggestCompositionMove(int compoId, int trackId, int position, int cursorPosition, int snapDistance = -1);
371

372
    /* @brief Request clip insertion at given position. This action is undoable
373 374 375 376 377 378 379
       Returns true on success. If it fails, nothing is modified.
       @param binClipId id of the clip in the bin
       @param track Id of the track where to insert
       @param position Requested position
       @param ID return parameter of the id of the inserted clip
       @param logUndo if set to false, no undo object is stored
       @param refreshView whether the view should be refreshed
380
       @param useTargets: if true, the Audio/video split will occur on the set targets. Otherwise, they will be computed as an offset from the middle line
381
    */
382 383
    bool requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo = true, bool refreshView = false,
                              bool useTargets = true);
384
    /* Same function, but accumulates undo and redo*/
385
    bool requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo, bool refreshView, bool useTargets, Fun &undo,
386
                              Fun &redo);
387 388

protected:
Nicolas Carion's avatar
Nicolas Carion committed
389 390 391 392
    /* @brief Creates a new clip instance without inserting it.
       This action is undoable, returns true on success
       @param binClipId: Bin id of the clip to insert
       @param id: return parameter for the id of the newly created clip.
393
       @param state: The desired clip state (original, audio/video only).
Nicolas Carion's avatar
Nicolas Carion committed
394
     */
395
    bool requestClipCreation(const QString &binClipId, int &id, PlaylistState::ClipState state, double speed, Fun &undo, Fun &redo);
396

397
public:
398 399 400 401 402
    /* @brief Deletes the given clip or composition from the timeline.
       This action is undoable.
       Returns true on success. If it fails, nothing is modified.
       If the clip/composition is in a group, the call is deferred to requestGroupDeletion
       @param clipId is the ID of the clip/composition
403
       @param logUndo if set to false, no undo object is stored */
404
    Q_INVOKABLE bool requestItemDeletion(int itemId, bool logUndo = true);
405
    /* Same function, but accumulates undo and redo*/
406
    bool requestItemDeletion(int itemId, Fun &undo, Fun &redo);
407

408
    /* @brief Move a group to a specific position
409
       This action is undoable
410 411
       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.
412 413
       @param clipId is the id of the clip that triggers the group move
       @param groupId is the id of the group
414
       @param delta_track is the delta applied to the track index
415
       @param delta_pos is the requested position change
416
       @param updateView if set to false, no signal is sent to qml for the clip clipId
417
       @param logUndo if set to true, an undo object is created
418
       @param allowViewRefresh if false, the view will never get updated (useful for suggestMove)
419
    */
420 421
    bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool updateView = true, bool logUndo = true);
    bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo,
Nicolas Carion's avatar
Nicolas Carion committed
422
                          bool allowViewRefresh = true);
423

424
    /* @brief Deletes all clips inside the group that contains the given clip.
425
       This action is undoable
426 427
       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.
428
       @param clipId is the id of the clip that triggers the group deletion
429
    */
430
    Q_INVOKABLE bool requestGroupDeletion(int clipId, bool logUndo = true);
431
    bool requestGroupDeletion(int clipId, Fun &undo, Fun &redo);
432

433
    /* @brief Change the duration of an item (clip or composition)
434
       This action is undoable
Nicolas Carion's avatar
Nicolas Carion committed
435 436
       Returns the real size reached (can be different, if snapping occurs).
       If it fails, nothing is modified, and -1 is returned
437 438 439
       @param itemId is the ID of the item
       @param size is the new size of the item
       @param right is true if we change the right side of the item, false otherwise
440
       @param logUndo if set to true, an undo object is created
441
       @param snap if set to true, the resize order will be coerced to use the snapping grid
442 443
       if @param allowSingleResize is false, then the resize will also be applied to any clip in the same AV group (allow resizing audio and video at the same
       time)
444
    */
445
    Q_INVOKABLE int requestItemResize(int itemId, int size, bool right, bool logUndo = true, int snapDistance = -1, bool allowSingleResize = false);
446

447
    /* Same function, but accumulates undo and redo and doesn't deal with snapping*/
448
    bool requestItemResize(int itemId, int size, bool right, bool logUndo, Fun &undo, Fun &redo, bool blockUndo = false);
449

450
    /* @brief Group together a set of ids
451
       The ids are either a group ids or clip ids. The involved clip must already be inserted in a track
452
       This action is undoable
453
       Returns the group id on success, -1 if it fails and nothing is modified.
454 455 456
       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
    */
457
    int requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo = true, GroupType type = GroupType::Normal);
458
    int requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal);
459 460

    /* @brief Destruct the topmost group containing clip
461
       This action is undoable
462
       Returns true on success. If it fails, nothing is modified.
463 464
       @param id of the clip to degroup (all clips belonging to the same group will be ungrouped as well)
    */
465
    bool requestClipUngroup(int itemId, bool logUndo = true);
466
    /* Same function, but accumulates undo and redo*/
Nicolas Carion's avatar
Nicolas Carion committed
467
    bool requestClipUngroup(int itemId, Fun &undo, Fun &redo);
468 469
    // convenience functions for several ids at the same time
    bool requestClipsUngroup(const std::unordered_set<int> &itemIds, bool logUndo = true);
470

471 472 473 474 475 476
    /* @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)
    */
477
    bool requestTrackInsertion(int pos, int &id, const QString &trackName = QString(), bool audioTrack = false);
478
    /* Same function, but accumulates undo and redo*/
479
    bool requestTrackInsertion(int pos, int &id, const QString &trackName, bool audioTrack, Fun &undo, Fun &redo, bool updateView = true);
480 481 482 483 484

    /* @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.
485
       @param trackId id of the track to delete
486
    */
487
    bool requestTrackDeletion(int trackId);
488
    /* Same function, but accumulates undo and redo*/
489
    bool requestTrackDeletion(int trackId, Fun &undo, Fun &redo);
490

491 492 493 494
    /* @brief Get project duration
       Returns the duration in frames
    */
    int duration() const;
495
    static int seekDuration; // Duration after project end where seeking is allowed
496

497 498
    /* @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.
499
       @param clipId id of the clip to test
500
    */
501
    std::unordered_set<int> getGroupElements(int clipId);
502

503 504
    /* @brief Removes all the elements on the timeline (tracks and clips)
     */
505
    bool requestReset(Fun &undo, Fun &redo);
506 507 508 509 510
    /* @brief Updates the current the pointer to the current undo_stack
       Must be called for example when the doc change
    */
    void setUndoStack(std::weak_ptr<DocUndoStack> undo_stack);

511
protected:
512 513 514
    /* @brief Requests the best snapped position for a clip
       @param pos is the clip's requested position
       @param length is the clip's duration
515
       @param pts snap points to ignore (for example currently moved clip)
516
       @param snapDistance the maximum distance for a snap result, -1 for no snapping
517 518
       @returns best snap position or -1 if no snap point is near
     */
519
    int getBestSnapPos(int pos, int length, const std::vector<int> &pts = std::vector<int>(), int cursorPosition = 0, int snapDistance = -1);
520

521
public:
522 523 524
    /* @brief Requests the next snapped point
       @param pos is the current position
     */
525
    int getNextSnapPos(int pos);
526 527 528 529

    /* @brief Requests the previous snapped point
       @param pos is the current position
     */
530
    int getPreviousSnapPos(int pos);
531

532 533 534 535 536 537 538 539 540 541
    /* @brief Add a new snap point
       @param pos is the current position
     */
    void addSnap(int pos);

    /* @brief Remove snap point
       @param pos is the current position
     */
    void removeSnap(int pos);

542
    /* @brief Request composition insertion at given position.
543 544
       This action is undoable
       Returns true on success. If it fails, nothing is modified.
545 546 547
       @param transitionId Identifier of the Mlt transition to insert (as given by repository)
       @param track Id of the track where to insert
       @param position Requested position
548
       @param length Requested initial length.
549 550
       @param id return parameter of the id of the inserted composition
       @param logUndo if set to false, no undo object is stored
551
    */
552
    bool requestCompositionInsertion(const QString &transitionId, int trackId, int position, int length, std::unique_ptr<Mlt::Properties> transProps, int &id,
553
                                     bool logUndo = true);
554
    /* Same function, but accumulates undo and redo*/
555 556
    bool requestCompositionInsertion(const QString &transitionId, int trackId, int compositionTrack, int position, int length,
                                     std::unique_ptr<Mlt::Properties> transProps, int &id, Fun &undo, Fun &redo, bool finalMove = false);
557

558 559 560 561
    /* @brief This function change the global (timeline-wise) enabled state of the effects
       It disables/enables track and clip effects (recursively)
     */
    void setTimelineEffectsEnabled(bool enabled);
562

563
    /* @brief Get a timeline clip id by its position or -1 if not found
564
     */
565
    int getClipByPosition(int trackId, int position) const;
566

567 568 569 570
    /* @brief Get a timeline composition id by its starting position or -1 if not found
     */
    int getCompositionByPosition(int trackId, int position) const;

Nicolas Carion's avatar
Nicolas Carion committed
571
    /* @brief Returns a list of all items that are intersect with a given range.
572
     * @param trackId is the id of the track for concerned items. Setting trackId to -1 returns items on all tracks
Nicolas Carion's avatar
Nicolas Carion committed
573
     * @param start is the position where we the items should start
574
     * @param end is the position after which items will not be selected, set to -1 to get all clips on track
575 576
     * @param listCompositions if enabled, the list will also contains composition ids
     */
Nicolas Carion's avatar
Nicolas Carion committed
577
    std::unordered_set<int> getItemsInRange(int trackId, int start, int end = -1, bool listCompositions = true);
578

579 580 581
    /* @brief Returns a list of all luma files used in the project
     */
    QStringList extractCompositionLumas() const;
582 583
    /* @brief Inform asset view of duration change
     */
584
    virtual void adjustAssetRange(int clipId, int in, int out);
585

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
586
    void requestClipReload(int clipId);
587
    void requestClipUpdate(int clipId, const QVector<int> &roles);
588 589 590
    /** @brief define current edit mode (normal, insert, overwrite */
    void setEditMode(TimelineMode::EditMode mode);
    Q_INVOKABLE bool normalEdit() const;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
591

592 593
    /** @brief Returns the effectstack of a given clip. */
    std::shared_ptr<EffectStackModel> getClipEffectStack(int itemId);
594
    std::shared_ptr<EffectStackModel> getTrackEffectStackModel(int trackId);
595

Nicolas Carion's avatar
Nicolas Carion committed
596 597 598 599
    /** @brief Add slowmotion effect to clip in timeline.
     @param clipId id of the target clip
    @param speed: speed in percentage. 100 corresponds to original speed, 50 to half the speed
    This functions create an undo object and also apply the effect to the corresponding audio if there is any.
600
    Returns true on success, false otherwise (and nothing is modified)
Nicolas Carion's avatar
Nicolas Carion committed
601 602 603 604
    */
    bool requestClipTimeWarp(int clipId, double speed);
    /* @brief Same function as above, but doesn't check for paired audio and accumulate undo/redo
     */
605
    bool requestClipTimeWarp(int clipId, double speed, Fun &undo, Fun &redo);
Nicolas Carion's avatar
Nicolas Carion committed
606

607
    void replugClip(int clipId);
608

609 610
    /** @brief Refresh the tractor profile in case a change was requested. */
    void updateProfile(Mlt::Profile *profile);
611

612 613 614
    /** @brief Clear the current selection
        @param onDeletion is true when the selection is cleared as a result of a deletion
     */
615
    Q_INVOKABLE bool requestClearSelection(bool onDeletion = false);
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
    // same function with undo/redo accumulation
    void requestClearSelection(bool onDeletion, Fun &undo, Fun &redo);

    /** @brief Add the given item to the selection
        If @param clear is true, the selection is first cleared
     */
    Q_INVOKABLE void requestAddToSelection(int itemId, bool clear = false);

    /** @brief Remove the given item from the selection */
    Q_INVOKABLE void requestRemoveFromSelection(int itemId);

    /** @brief Set the selection to the set of given ids */
    bool requestSetSelection(const std::unordered_set<int> &ids);
    // same function with undo/redo
    bool requestSetSelection(const std::unordered_set<int> &ids, Fun &undo, Fun &redo);

    /** @brief Returns a set containing all the items in the selection */
    std::unordered_set<int> getCurrentSelection() const;

635 636 637 638
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.
     */
639
    void registerTrack(std::shared_ptr<TrackModel> track, int pos = -1, bool doInsert = true, bool reloadView = true);
640

641
    /* @brief Register a new clip. This is a call-back meant to be called from ClipModel
642
     */
643
    void registerClip(const std::shared_ptr<ClipModel> &clip, bool registerProducer = false);
644

645
    /* @brief Register a new composition. This is a call-back meant to be called from CompositionModel
646
     */
Nicolas Carion's avatar
Nicolas Carion committed
647
    void registerComposition(const std::shared_ptr<CompositionModel> &composition);
648

649 650 651 652
    /* @brief Register a new group. This is a call-back meant to be called from GroupsModel
     */
    void registerGroup(int groupId);

653
    /* @brief Deregister and destruct the track with given id.
654
       @parame updateView Whether to send updates to the model. Must be false when called from a constructor/destructor
655
     */
656
    Fun deregisterTrack_lambda(int id, bool updateView = false);
657

658 659
    /* @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.
660
     */
661
    Fun deregisterClip_lambda(int id);
662

663
    /* @brief Return a lambda that deregisters and destructs the composition with given id.
664
     */
665
    Fun deregisterComposition_lambda(int compoId);
666

667 668 669 670
    /* @brief Deregister a group with given id
     */
    void deregisterGroup(int id);

671 672
    /* @brief Helper function to get a pointer to the track, given its id
     */
673 674
    std::shared_ptr<TrackModel> getTrackById(int trackId);
    const std::shared_ptr<TrackModel> getTrackById_const(int trackId) const;
675

676
    /*@brief Helper function to get a pointer to a clip, given its id*/
677
    std::shared_ptr<ClipModel> getClipPtr(int clipId) const;
678

679 680
    /*@brief Helper function to get a pointer to a composition, given its id*/
    std::shared_ptr<CompositionModel> getCompositionPtr(int compoId) const;
681

682 683 684
    /* @brief Returns next valid unique id to create an object
     */
    static int getNextId();
685

686 687 688
    /* @brief unplant and the replant all the compositions in the correct order
       @param currentCompo is the id of a compo that have not yet been planted, if any. Otherwise send -1
     */
689
    bool replantCompositions(int currentCompo, bool updateView);
690 691 692 693

    /* @brief Unplant the composition with given Id */
    bool unplantComposition(int compoId);

694
    /* Internal functions to delete a clip or a composition. In general, you should call requestItemDeletion */
695 696
    bool requestClipDeletion(int clipId, Fun &undo, Fun &redo);
    bool requestCompositionDeletion(int compositionId, Fun &undo, Fun &redo);
697

698 699
    /** @brief Check tracks duration and update black track accordingly */
    void updateDuration();
700 701
    /** @brief Get a track tag (A1, V1, V2,...) through its id */
    const QString getTrackTagById(int trackId) const;
702

703 704 705
    /** @brief Attempt to make a clip move without ever updating the view */
    bool requestClipMoveAttempt(int clipId, int trackId, int position);

706
public:
707 708
    /* @brief Debugging function that checks consistency with Mlt objects */
    bool checkConsistency();
709

710
protected:
711
    /* @brief Refresh project monitor if cursor was inside range */
712
    void checkRefresh(int start, int end);
713

714
    /* @brief Send signal to require clearing effet/composition view */
715 716
    void clearAssetView(int itemId);

717 718
    bool m_blockRefresh;

719 720 721 722
signals:
    /* @brief signal triggered by clearAssetView */
    void requestClearAssetView(int);
    void requestMonitorRefresh();
723
    /* @brief signal triggered by track operations */
724
    void invalidateZone(int in, int out);
725 726
    /* @brief signal triggered when a track duration changed (insertion/deletion) */
    void durationUpdated();
727 728 729

    /* @brief Signal sent whenever the selection changes */
    void selectionChanged();
730

731
protected:
732
    std::unique_ptr<Mlt::Tractor> m_tractor;
733

734
    std::list<std::shared_ptr<TrackModel>> m_allTracks;
735

Nicolas Carion's avatar
Nicolas Carion committed
736 737
    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.
738

Nicolas Carion's avatar
Nicolas Carion committed
739
    std::unordered_map<int, std::shared_ptr<ClipModel>> m_allClips; // the keys are the clip id, and the values are the corresponding pointers
740

Nicolas Carion's avatar
Nicolas Carion committed
741 742
    std::unordered_map<int, std::shared_ptr<CompositionModel>>
        m_allCompositions; // the keys are the composition id, and the values are the corresponding pointers
743

Nicolas Carion's avatar
Nicolas Carion committed
744
    static int next_id; // next valid id to assign
745 746

    std::unique_ptr<GroupsModel> m_groups;
747
    std::shared_ptr<SnapModel> m_snaps;
748

Nicolas Carion's avatar
Nicolas Carion committed
749
    std::unordered_set<int> m_allGroups; // ids of all the groups
750

751 752
    std::weak_ptr<DocUndoStack> m_undoStack;

753
    Mlt::Profile *m_profile;
754

755
    // The black track producer. Its length / out should always be adjusted to the projects's length
756
    std::unique_ptr<Mlt::Producer> m_blackClip;
757

Nicolas Carion's avatar
Nicolas Carion committed
758
    mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
Nicolas Carion's avatar
Nicolas Carion committed
759

760 761
    bool m_timelineEffectsEnabled;

Nicolas Carion's avatar
Nicolas Carion committed
762 763
    bool m_id; // id of the timeline itself

764 765 766 767
    // id of the selection. If -1, there is no selection, if positive, then it might either be the id of the selection group, or the id of an individual
    // item, or, finally, the id of a group which is not of type selection. The last case happens when the selection exactly matches an existing group
    // (in that case we cannot further group it because the selection would have only one child, which is prohibited by design)
    int m_currentSelection = -1;
768

769
    // The index of the temporary overlay track in tractor, or -1 if not connected
770
    int m_overlayTrackCount;
771

772 773 774 775
    // The preferred audio target for clip insertion or -1 if not defined
    int m_audioTarget;
    // The preferred video target for clip insertion or -1 if not defined
    int m_videoTarget;
776 777
    // Timeline editing mode
    TimelineMode::EditMode m_editMode;
778

Nicolas Carion's avatar
Nicolas Carion committed
779
    // what follows are some virtual function that corresponds to the QML. They are implemented in TimelineItemModel
780
protected:
781
    virtual void _beginRemoveRows(const QModelIndex &, int, int) = 0;
782
    virtual void _beginInsertRows(const QModelIndex &, int, int) = 0;
783
    virtual void _endRemoveRows() = 0;
784 785
    virtual void _endInsertRows() = 0;
    virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) = 0;
786
    virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) = 0;
787
    virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) = 0;
788
    virtual QModelIndex makeClipIndexFromID(int) const = 0;
789
    virtual QModelIndex makeCompositionIndexFromID(int) const = 0;
Nicolas Carion's avatar
Nicolas Carion committed
790
    virtual QModelIndex makeTrackIndexFromID(int) const = 0;
791
    virtual void _resetView() = 0;
792
};
793
#endif