timelinemodel.cpp 155 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/***************************************************************************
 *   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/>. *
 ***************************************************************************/

#include "timelinemodel.hpp"
23
#include "assets/model/assetparametermodel.hpp"
24
#include "bin/projectclip.h"
25
#include "bin/projectitemmodel.h"
26
#include "clipmodel.hpp"
27
#include "compositionmodel.hpp"
Nicolas Carion's avatar
style  
Nicolas Carion committed
28
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
29
#include "doc/docundostack.hpp"
30
#include "effects/effectsrepository.hpp"
31
#include "effects/effectstack/model/effectstackmodel.hpp"
32
#include "groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
33
#include "kdenlivesettings.h"
34
#include "logger.hpp"
35
#include "snapmodel.hpp"
36
#include "timelinefunctions.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
37
#include "trackmodel.hpp"
38

39
#include <QDebug>
40
#include <QThread>
41
#include <QModelIndex>
Nicolas Carion's avatar
Nicolas Carion committed
42
#include <klocalizedstring.h>
Nicolas Carion's avatar
Nicolas Carion committed
43
#include <mlt++/MltConsumer.h>
Nicolas Carion's avatar
Nicolas Carion committed
44
#include <mlt++/MltField.h>
45
#include <mlt++/MltProfile.h>
Nicolas Carion's avatar
Nicolas Carion committed
46
#include <mlt++/MltTractor.h>
47
#include <mlt++/MltTransition.h>
48
#include <queue>
49

50 51
#include "macros.hpp"

Nicolas Carion's avatar
Nicolas Carion committed
52 53 54 55 56 57 58 59 60 61 62 63
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include <rttr/registration>
#pragma GCC diagnostic pop
RTTR_REGISTRATION
{
    using namespace rttr;
    registration::class_<TimelineModel>("TimelineModel")
64
        .method("setTrackLockedState", &TimelineModel::setTrackLockedState)(parameter_names("trackId", "lock"))
65 66
        .method("requestClipMove", select_overload<bool(int, int, int, bool, bool, bool, bool)>(&TimelineModel::requestClipMove))(
            parameter_names("clipId", "trackId", "position", "moveMirrorTracks", "updateView", "logUndo", "invalidateTimeline"))
Nicolas Carion's avatar
Nicolas Carion committed
67 68 69 70 71
        .method("requestCompositionMove", select_overload<bool(int, int, int, bool, bool)>(&TimelineModel::requestCompositionMove))(
            parameter_names("compoId", "trackId", "position", "updateView", "logUndo"))
        .method("requestClipInsertion", select_overload<bool(const QString &, int, int, int &, bool, bool, bool)>(&TimelineModel::requestClipInsertion))(
            parameter_names("binClipId", "trackId", "position", "id", "logUndo", "refreshView", "useTargets"))
        .method("requestItemDeletion", select_overload<bool(int, bool)>(&TimelineModel::requestItemDeletion))(parameter_names("clipId", "logUndo"))
72 73
        .method("requestGroupMove", select_overload<bool(int, int, int, int, bool, bool, bool)>(&TimelineModel::requestGroupMove))(
            parameter_names("itemId", "groupId", "delta_track", "delta_pos", "moveMirrorTracks", "updateView", "logUndo"))
Nicolas Carion's avatar
Nicolas Carion committed
74 75 76 77
        .method("requestGroupDeletion", select_overload<bool(int, bool)>(&TimelineModel::requestGroupDeletion))(parameter_names("clipId", "logUndo"))
        .method("requestItemResize", select_overload<int(int, int, bool, bool, int, bool)>(&TimelineModel::requestItemResize))(
            parameter_names("itemId", "size", "right", "logUndo", "snapDistance", "allowSingleResize"))
        .method("requestClipsGroup", select_overload<int(const std::unordered_set<int> &, bool, GroupType)>(&TimelineModel::requestClipsGroup))(
78 79 80
            parameter_names("itemIds", "logUndo", "type"))
        .method("requestClipUngroup", select_overload<bool(int, bool)>(&TimelineModel::requestClipUngroup))(parameter_names("itemId", "logUndo"))
        .method("requestClipsUngroup", &TimelineModel::requestClipsUngroup)(parameter_names("itemIds", "logUndo"))
Nicolas Carion's avatar
Nicolas Carion committed
81 82
        .method("requestTrackInsertion", select_overload<bool(int, int &, const QString &, bool)>(&TimelineModel::requestTrackInsertion))(
            parameter_names("pos", "id", "trackName", "audioTrack"))
83
        .method("requestTrackDeletion", select_overload<bool(int)>(&TimelineModel::requestTrackDeletion))(parameter_names("trackId"))
84
        .method("requestClearSelection", select_overload<bool(bool)>(&TimelineModel::requestClearSelection))(parameter_names("onDeletion"))
85 86
        .method("requestAddToSelection", &TimelineModel::requestAddToSelection)(parameter_names("itemId", "clear"))
        .method("requestRemoveFromSelection", &TimelineModel::requestRemoveFromSelection)(parameter_names("itemId"))
87 88 89
        .method("requestSetSelection", select_overload<bool(const std::unordered_set<int> &)>(&TimelineModel::requestSetSelection))(parameter_names("itemIds"))
        .method("requestFakeClipMove", select_overload<bool(int, int, int, bool, bool, bool)>(&TimelineModel::requestFakeClipMove))(
            parameter_names("clipId", "trackId", "position", "updateView", "logUndo", "invalidateTimeline"))
90 91
        .method("requestFakeGroupMove", select_overload<bool(int, int, int, int, bool, bool)>(&TimelineModel::requestFakeGroupMove))(
            parameter_names("clipId", "groupId", "delta_track", "delta_pos", "updateView", "logUndo"))
92
        .method("suggestClipMove", &TimelineModel::suggestClipMove)(parameter_names("clipId", "trackId", "position", "cursorPosition", "snapDistance", "moveMirrorTracks"))
93 94
        .method("suggestCompositionMove",
                &TimelineModel::suggestCompositionMove)(parameter_names("compoId", "trackId", "position", "cursorPosition", "snapDistance"))
95 96
        // .method("addSnap", &TimelineModel::addSnap)(parameter_names("pos"))
        // .method("removeSnap", &TimelineModel::addSnap)(parameter_names("pos"))
97 98 99
        // .method("requestCompositionInsertion", select_overload<bool(const QString &, int, int, int, std::unique_ptr<Mlt::Properties>, int &, bool)>(
        //                                            &TimelineModel::requestCompositionInsertion))(
        //     parameter_names("transitionId", "trackId", "position", "length", "transProps", "id", "logUndo"))
100
        .method("requestClipTimeWarp", select_overload<bool(int, double,bool,bool)>(&TimelineModel::requestClipTimeWarp))(parameter_names("clipId", "speed","pitchCompensate","changeDuration"));
Nicolas Carion's avatar
Nicolas Carion committed
101 102
}

103
int TimelineModel::next_id = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
104
int TimelineModel::seekDuration = 30000;
105

106
TimelineModel::TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack)
107
    : QAbstractItemModel_shared_from_this()
Vincent Pinon's avatar
Vincent Pinon committed
108
    , m_blockRefresh(false)
109
    , m_tractor(new Mlt::Tractor(*profile))
110
    , m_masterStack(nullptr)
111
    , m_snaps(new SnapModel())
Nicolas Carion's avatar
Nicolas Carion committed
112
    , m_undoStack(std::move(undo_stack))
113
    , m_profile(profile)
114
    , m_blackClip(new Mlt::Producer(*profile, "color:black"))
115 116
    , m_lock(QReadWriteLock::Recursive)
    , m_timelineEffectsEnabled(true)
Nicolas Carion's avatar
Nicolas Carion committed
117
    , m_id(getNextId())
118
    , m_overlayTrackCount(-1)
119
    , m_videoTarget(-1)
120
    , m_editMode(TimelineMode::NormalEdit)
121
    , m_closing(false)
122
{
123 124 125
    // Create black background track
    m_blackClip->set("id", "black_track");
    m_blackClip->set("mlt_type", "producer");
126
    m_blackClip->set("aspect_ratio", 1);
127
    m_blackClip->set("length", INT_MAX);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
128
    m_blackClip->set("mlt_image_format", "rgb24a");
129
    m_blackClip->set("set.test_audio", 0);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
130
    m_blackClip->set_in_and_out(0, TimelineModel::seekDuration);
131
    m_tractor->insert_track(*m_blackClip, 0);
132

133
    TRACE_CONSTR(this);
134 135
}

136 137
void TimelineModel::prepareClose()
{
138
    requestClearSelection(true);
139
    QWriteLocker locker(&m_lock);
140
    // Unlock all tracks to allow deleting clip from tracks
141 142 143
    m_closing = true;
    auto it = m_allTracks.begin();
    while (it != m_allTracks.end()) {
144
        (*it)->unlock();
145 146 147 148
        ++it;
    }
}

149 150
TimelineModel::~TimelineModel()
{
151
    std::vector<int> all_ids;
Nicolas Carion's avatar
Nicolas Carion committed
152
    for (auto tracks : m_iteratorTable) {
153 154
        all_ids.push_back(tracks.first);
    }
Nicolas Carion's avatar
Nicolas Carion committed
155
    for (auto tracks : all_ids) {
156
        deregisterTrack_lambda(tracks)();
157
    }
158
    for (const auto &clip : m_allClips) {
159 160
        clip.second->deregisterClipToBin();
    }
161 162
}

163 164
int TimelineModel::getTracksCount() const
{
165
    READ_LOCK();
166
    int count = m_tractor->count();
167 168
    if (m_overlayTrackCount > -1) {
        count -= m_overlayTrackCount;
169
    }
170
    Q_ASSERT(count >= 0);
171
    // don't count the black background track
Nicolas Carion's avatar
Nicolas Carion committed
172
    Q_ASSERT(count - 1 == static_cast<int>(m_allTracks.size()));
173
    return count - 1;
174
}
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
QPair<int, int> TimelineModel::getAVtracksCount() const
{
    QPair <int, int> tracks{0, 0};
    auto it = m_allTracks.cbegin();
    while (it != m_allTracks.cend()) {
        if ((*it)->isAudioTrack()) {
            tracks.second++;
        } else {
            tracks.first++;
        }
        ++it;
    }
    if (m_overlayTrackCount > -1) {
        tracks.first -= m_overlayTrackCount;
    }
    return tracks;
}

QList<int> TimelineModel::getTracksIds(bool audio) const
{
    QList <int> trackIds;
    auto it = m_allTracks.cbegin();
    while (it != m_allTracks.cend()) {
        if ((*it)->isAudioTrack() == audio) {
            trackIds.insert(-1, (*it)->getId());
        }
        ++it;
    }
    return trackIds;
}

207 208
int TimelineModel::getTrackIndexFromPosition(int pos) const
{
209
    Q_ASSERT(pos >= 0 && pos < (int)m_allTracks.size());
210
    READ_LOCK();
211
    auto it = m_allTracks.cbegin();
212 213 214 215 216 217 218
    while (pos > 0) {
        it++;
        pos--;
    }
    return (*it)->getId();
}

219
int TimelineModel::getClipsCount() const
220
{
221 222 223
    READ_LOCK();
    int size = int(m_allClips.size());
    return size;
224
}
225

226 227 228 229 230 231
int TimelineModel::getCompositionsCount() const
{
    READ_LOCK();
    int size = int(m_allCompositions.size());
    return size;
}
232

233
int TimelineModel::getClipTrackId(int clipId) const
234
{
235
    READ_LOCK();
236 237
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
238
    return clip->getCurrentTrackId();
239 240
}

241 242 243 244 245 246 247 248 249 250
int TimelineModel::getCompositionTrackId(int compoId) const
{
    Q_ASSERT(m_allCompositions.count(compoId) > 0);
    const auto trans = m_allCompositions.at(compoId);
    return trans->getCurrentTrackId();
}

int TimelineModel::getItemTrackId(int itemId) const
{
    READ_LOCK();
251
    Q_ASSERT(isItem(itemId));
252 253 254 255 256 257
    if (isComposition(itemId)) {
        return getCompositionTrackId(itemId);
    }
    return getClipTrackId(itemId);
}

258
int TimelineModel::getClipPosition(int clipId) const
259
{
260
    READ_LOCK();
261 262
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
263 264
    int pos = clip->getPosition();
    return pos;
265 266
}

267 268 269 270 271 272 273
double TimelineModel::getClipSpeed(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_allClips.at(clipId)->getSpeed();
}

274 275 276 277 278 279 280
int TimelineModel::getClipSplitPartner(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_groups->getSplitPartner(clipId);
}

281 282 283 284 285
int TimelineModel::getClipIn(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
286 287 288 289 290 291 292 293 294
    return clip->getIn();
}

PlaylistState::ClipState TimelineModel::getClipState(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
    return clip->clipState();
295 296 297 298 299 300 301 302 303 304 305
}

const QString TimelineModel::getClipBinId(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
    QString id = clip->binId();
    return id;
}

306
int TimelineModel::getClipPlaytime(int clipId) const
307
{
308
    READ_LOCK();
309
    Q_ASSERT(isClip(clipId));
310
    const auto clip = m_allClips.at(clipId);
311 312
    int playtime = clip->getPlaytime();
    return playtime;
313 314
}

315 316 317 318 319 320 321 322
QSize TimelineModel::getClipFrameSize(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(isClip(clipId));
    const auto clip = m_allClips.at(clipId);
    return clip->getFrameSize();
}

323
int TimelineModel::getTrackClipsCount(int trackId) const
324
{
325
    READ_LOCK();
326
    Q_ASSERT(isTrack(trackId));
327
    int count = getTrackById_const(trackId)->getClipsCount();
328
    return count;
329 330
}

331 332 333 334 335 336 337
int TimelineModel::getClipByPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getClipByPosition(position);
}

338 339 340 341 342 343 344
int TimelineModel::getCompositionByPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getCompositionByPosition(position);
}

345
int TimelineModel::getTrackPosition(int trackId) const
346
{
347
    READ_LOCK();
348
    Q_ASSERT(isTrack(trackId));
349
    auto it = m_allTracks.cbegin();
350
    int pos = (int)std::distance(it, (decltype(it))m_iteratorTable.at(trackId));
351
    return pos;
352 353
}

354 355 356 357 358 359 360
int TimelineModel::getTrackMltIndex(int trackId) const
{
    READ_LOCK();
    // Because of the black track that we insert in first position, the mlt index is the position + 1
    return getTrackPosition(trackId) + 1;
}

361
int TimelineModel::getTrackSortValue(int trackId, int separated) const
362
{
363
    if (separated == 1) {
364
        // This will be A2, A1, V1, V2
365 366
        return getTrackPosition(trackId) + 1;
    }
367
    if (separated == 2) {
368
        // This will be A1, A2, V1, V2
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
        // Count audio/video tracks
        auto it = m_allTracks.cbegin();
        int aCount = 0;
        int vCount = 0;
        int refPos = 0;
        bool isVideo = true;
        while (it != m_allTracks.cend()) {
            if ((*it)->isAudioTrack()) {
                if ((*it)->getId() == trackId) {
                    refPos = aCount;
                    isVideo = false;
                }
                aCount++;
            } else {
                // video track
                if ((*it)->getId() == trackId) {
                    refPos = vCount;
                }
                vCount++;
            }
            ++it;
        }
        return isVideo ? aCount + refPos + 1 : aCount - refPos;
    }
393
    // This will be A1, V1, A2, V2
394
    auto it = m_allTracks.cend();
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    int aCount = 0;
    int vCount = 0;
    bool isAudio = false;
    int trackPos = 0;
    while (it != m_allTracks.begin()) {
        --it;
        bool audioTrack = (*it)->isAudioTrack();
        if (audioTrack) {
            aCount++;
        } else {
            vCount++;
        }
        if (trackId == (*it)->getId()) {
            isAudio = audioTrack;
            trackPos = audioTrack ? aCount : vCount;
        }
    }
412
    int trackDiff = qMax(0, aCount - vCount);
413
    if (trackDiff > 0) {
414 415
        // more audio tracks, keep them below
        if (isAudio && trackPos > vCount) {
416 417 418
            return -trackPos;
        }
    }
419
    return isAudio ? 2 * trackPos : 2 * (vCount + 1 - trackPos) + 1;
420 421
}

Nicolas Carion's avatar
Nicolas Carion committed
422
QList<int> TimelineModel::getLowerTracksId(int trackId, TrackType type) const
423 424 425
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
Nicolas Carion's avatar
Nicolas Carion committed
426
    QList<int> results;
427
    auto it = m_iteratorTable.at(trackId);
428
    while (it != m_allTracks.cbegin()) {
429 430
        --it;
        if (type == TrackType::AnyTrack) {
431 432
            results << (*it)->getId();
            continue;
433
        }
434 435
        bool audioTrack = (*it)->isAudioTrack();
        if (type == TrackType::AudioTrack && audioTrack) {
436
            results << (*it)->getId();
437
        } else if (type == TrackType::VideoTrack && !audioTrack) {
438
            results << (*it)->getId();
439 440
        }
    }
441
    return results;
442 443
}

444 445 446 447 448
int TimelineModel::getPreviousVideoTrackIndex(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
449
    while (it != m_allTracks.cbegin()) {
450
        --it;
451 452
        if (!(*it)->isAudioTrack()) {
            return (*it)->getId();
453 454
        }
    }
455
    return 0;
456 457
}

458
int TimelineModel::getPreviousVideoTrackPos(int trackId) const
459 460 461 462
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
463
    while (it != m_allTracks.cbegin()) {
464
        --it;
465 466
        if (!(*it)->isAudioTrack()) {
            return getTrackMltIndex((*it)->getId());
467
        }
468
    }
469
    return 0;
470 471
}

472 473 474 475 476 477 478 479 480 481
int TimelineModel::getMirrorVideoTrackId(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
    if (!(*it)->isAudioTrack()) {
        // we expected an audio track...
        return -1;
    }
    int count = 0;
482
    while (it != m_allTracks.cend()) {
483 484 485
        if ((*it)->isAudioTrack()) {
            count++;
        } else {
486
            count--;
487 488 489 490 491 492 493 494 495
            if (count == 0) {
                return (*it)->getId();
            }
        }
        ++it;
    }
    return -1;
}

496 497 498 499 500 501 502 503
int TimelineModel::getMirrorTrackId(int trackId) const
{
    if (isAudioTrack(trackId)) {
        return getMirrorVideoTrackId(trackId);
    }
    return getMirrorAudioTrackId(trackId);
}

504 505 506 507 508 509 510
int TimelineModel::getMirrorAudioTrackId(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
    if ((*it)->isAudioTrack()) {
        // we expected a video track...
511
        qDebug()<<"++++++++\n+++++++ ERROR RQSTNG AUDIO MIRROR FOR AUDIO";
512 513 514
        return -1;
    }
    int count = 0;
515
    while (it != m_allTracks.cbegin()) {
516 517 518
        if (!(*it)->isAudioTrack()) {
            count++;
        } else {
519
            count--;
520 521 522 523
            if (count == 0) {
                return (*it)->getId();
            }
        }
524
        --it;
525
    }
526
    if ((*it)->isAudioTrack() && count == 1) {
527 528
        return (*it)->getId();
    }
529 530 531
    return -1;
}

532 533 534 535 536 537 538 539 540 541
void TimelineModel::setEditMode(TimelineMode::EditMode mode)
{
    m_editMode = mode;
}

bool TimelineModel::normalEdit() const
{
    return m_editMode == TimelineMode::NormalEdit;
}

542
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo)
543
{
544 545 546 547
    Q_UNUSED(updateView);
    Q_UNUSED(invalidateTimeline);
    Q_UNUSED(undo);
    Q_UNUSED(redo);
548 549 550 551 552
    Q_ASSERT(isClip(clipId));
    m_allClips[clipId]->setFakePosition(position);
    bool trackChanged = false;
    if (trackId > -1) {
        if (trackId != m_allClips[clipId]->getFakeTrackId()) {
553
            if (getTrackById_const(trackId)->trackType() == m_allClips[clipId]->clipState()) {
554 555 556 557 558 559 560
                m_allClips[clipId]->setFakeTrackId(trackId);
                trackChanged = true;
            }
        }
    }
    QModelIndex modelIndex = makeClipIndexFromID(clipId);
    if (modelIndex.isValid()) {
561
        QVector<int> roles{FakePositionRole};
562 563 564 565 566 567 568 569 570
        if (trackChanged) {
            roles << FakeTrackIdRole;
        }
        notifyChange(modelIndex, modelIndex, roles);
        return true;
    }
    return false;
}

571
bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool groupMove)
572
{
Vincent Pinon's avatar
Vincent Pinon committed
573
    Q_UNUSED(moveMirrorTracks)
574
    // qDebug() << "// FINAL MOVE: " << invalidateTimeline << ", UPDATE VIEW: " << updateView<<", FINAL: "<<finalMove;
575 576 577
    if (trackId == -1) {
        return false;
    }
578
    Q_ASSERT(isClip(clipId));
579 580 581 582 583 584 585 586
    if (m_allClips[clipId]->clipState() == PlaylistState::Disabled) {
        if (getTrackById_const(trackId)->trackType() == PlaylistState::AudioOnly && !m_allClips[clipId]->canBeAudio()) {
            return false;
        }
        if (getTrackById_const(trackId)->trackType() == PlaylistState::VideoOnly && !m_allClips[clipId]->canBeVideo()) {
            return false;
        }
    } else if (getTrackById_const(trackId)->trackType() != m_allClips[clipId]->clipState()) {
587
        // Move not allowed (audio / video mismatch)
588
        //qDebug() << "// CLIP MISMATC FOR TK: "<<trackId<< ", " << getTrackById_const(trackId)->trackType() << " == " << m_allClips[clipId]->clipState();
589 590
        return false;
    }
Nicolas Carion's avatar
Nicolas Carion committed
591 592
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
593
    bool ok = true;
594
    int old_trackId = getClipTrackId(clipId);
595
    bool notifyViewOnly = false;
596
    // qDebug()<<"MOVING CLIP FROM: "<<old_trackId<<" == "<<trackId;
597 598 599
    Fun update_model = []() { return true; };
    if (old_trackId == trackId) {
        // Move on same track, simply inform the view
Nicolas Carion's avatar
Nicolas Carion committed
600
        updateView = false;
601
        notifyViewOnly = true;
602
        update_model = [clipId, this, trackId, invalidateTimeline]() {
603
            QModelIndex modelIndex = makeClipIndexFromID(clipId);
Nicolas Carion's avatar
Nicolas Carion committed
604
            notifyChange(modelIndex, modelIndex, StartRole);
605
            if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
606 607 608
                int in = getClipPosition(clipId);
                emit invalidateZone(in, in + getClipPlaytime(clipId));
            }
609 610 611
            return true;
        };
    }
612
    if (old_trackId != -1) {
613 614 615
        if (notifyViewOnly) {
            PUSH_LAMBDA(update_model, local_undo);
        }
616
        ok = getTrackById(old_trackId)->requestClipDeletion(clipId, updateView, finalMove, local_undo, local_redo, groupMove, false);
617
        if (!ok) {
618 619
            bool undone = local_undo();
            Q_ASSERT(undone);
620 621 622
            return false;
        }
    }
623
    ok = getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove);
624
    if (!ok) {
625
        qDebug() << "-------------\n\nINSERTION FAILED, REVERTING\n\n-------------------";
626 627
        bool undone = local_undo();
        Q_ASSERT(undone);
628
        return false;
629
    }
630 631 632 633
    update_model();
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
Nicolas Carion's avatar
Nicolas Carion committed
634 635
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return true;
636 637
}

638 639 640
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool logUndo, bool invalidateTimeline)
{
    QWriteLocker locker(&m_lock);
641
    TRACE(clipId, trackId, position, updateView, logUndo, invalidateTimeline)
642 643 644 645 646 647 648 649 650
    Q_ASSERT(m_allClips.count(clipId) > 0);
    if (m_groups->isInGroup(clipId)) {
        // element is in a group.
        int groupId = m_groups->getRootId(clipId);
        int current_trackId = getClipTrackId(clipId);
        int track_pos1 = getTrackPosition(trackId);
        int track_pos2 = getTrackPosition(current_trackId);
        int delta_track = track_pos1 - track_pos2;
        int delta_pos = position - m_allClips[clipId]->getPosition();
651 652 653
        bool res = requestFakeGroupMove(clipId, groupId, delta_track, delta_pos, updateView, logUndo);
        TRACE_RES(res);
        return res;
654 655 656
    }
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
657
    bool res = requestFakeClipMove(clipId, trackId, position, updateView, invalidateTimeline, undo, redo);
658 659 660
    if (res && logUndo) {
        PUSH_UNDO(undo, redo, i18n("Move clip"));
    }
661
    TRACE_RES(res);
662 663 664
    return res;
}

665
bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks, bool updateView, bool logUndo, bool invalidateTimeline)
666
{
667
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
668
    TRACE(clipId, trackId, position, updateView, logUndo, invalidateTimeline);
669 670
    Q_ASSERT(m_allClips.count(clipId) > 0);
    if (m_allClips[clipId]->getPosition() == position && getClipTrackId(clipId) == trackId) {
Nicolas Carion's avatar
Nicolas Carion committed
671
        TRACE_RES(true);
672 673
        return true;
    }
674
    if (m_groups->isInGroup(clipId)) {
Nicolas Carion's avatar
Nicolas Carion committed
675
        // element is in a group.
676 677 678 679
        int groupId = m_groups->getRootId(clipId);
        int current_trackId = getClipTrackId(clipId);
        int track_pos1 = getTrackPosition(trackId);
        int track_pos2 = getTrackPosition(current_trackId);
680
        int delta_track = track_pos1 - track_pos2;
681
        int delta_pos = position - m_allClips[clipId]->getPosition();
682
        return requestGroupMove(clipId, groupId, delta_track, delta_pos, moveMirrorTracks, updateView, logUndo);
683
    }
Nicolas Carion's avatar
Nicolas Carion committed
684 685
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
686
    bool res = requestClipMove(clipId, trackId, position, moveMirrorTracks, updateView, invalidateTimeline, logUndo, undo, redo);
687 688
    if (res && logUndo) {
        PUSH_UNDO(undo, redo, i18n("Move clip"));
689
    }
Nicolas Carion's avatar
Nicolas Carion committed
690
    TRACE_RES(res);
691 692 693
    return res;
}

694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
bool TimelineModel::requestClipMoveAttempt(int clipId, int trackId, int position)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_allClips.count(clipId) > 0);
    if (m_allClips[clipId]->getPosition() == position && getClipTrackId(clipId) == trackId) {
        return true;
    }
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
    bool res = true;
    if (m_groups->isInGroup(clipId)) {
        // element is in a group.
        int groupId = m_groups->getRootId(clipId);
        int current_trackId = getClipTrackId(clipId);
        int track_pos1 = getTrackPosition(trackId);
        int track_pos2 = getTrackPosition(current_trackId);
        int delta_track = track_pos1 - track_pos2;
        int delta_pos = position - m_allClips[clipId]->getPosition();
712
        res = requestGroupMove(clipId, groupId, delta_track, delta_pos, false, false, undo, redo, false);
713
    } else {
714
        res = requestClipMove(clipId, trackId, position, true, false, false, false, undo, redo);
715 716 717 718 719 720 721
    }
    if (res) {
        undo();
    }
    return res;
}

722
QVariantList TimelineModel::suggestItemMove(int itemId, int trackId, int position, int cursorPosition, int snapDistance)
723 724
{
    if (isClip(itemId)) {
725
        return suggestClipMove(itemId, trackId, position, cursorPosition, snapDistance);
726
    }
727
    return suggestCompositionMove(itemId, trackId, position, cursorPosition, snapDistance);
728 729
}

730
QVariantList TimelineModel::suggestClipMove(int clipId, int trackId, int position, int cursorPosition, int snapDistance, bool moveMirrorTracks)
731
{
732
    QWriteLocker locker(&m_lock);
733
    TRACE(clipId, trackId, position, cursorPosition, snapDistance);
734 735
    Q_ASSERT(isClip(clipId));
    Q_ASSERT(isTrack(trackId));
736 737
    int currentPos = m_editMode == TimelineMode::NormalEdit ? getClipPosition(clipId) : m_allClips[clipId]->getFakePosition();
    int offset = m_editMode == TimelineMode::NormalEdit ? 0 : getClipPosition(clipId) - currentPos;
738
    int sourceTrackId = (m_editMode != TimelineMode::NormalEdit) ? m_allClips[clipId]->getFakeTrackId() : getClipTrackId(clipId);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
739
    if (sourceTrackId > -1 && getTrackById_const(trackId)->isAudioTrack() != getTrackById_const(sourceTrackId)->isAudioTrack()) {
740 741 742
        // Trying move on incompatible track type, stay on same track
        trackId = sourceTrackId;
    }
743
    if (currentPos == position && sourceTrackId == trackId) {
744
        TRACE_RES(position);
745
        return {position, trackId};
746
    }
747
    bool after = position > currentPos;
748 749
    if (snapDistance > 0) {
        std::vector<int> ignored_pts;
750
        // For snapping, we must ignore all in/outs of the clips of the group being moved
751
        std::unordered_set<int> all_items = {clipId};
752 753
        if (m_groups->isInGroup(clipId)) {
            int groupId = m_groups->getRootId(clipId);
754
            all_items = m_groups->getLeaves(groupId);
755
        }
756
        for (int current_clipId : all_items) {
757
            if (getItemTrackId(current_clipId) != -1) {
758
                if (isClip(current_clipId)) {
759
                    m_allClips[current_clipId]->allSnaps(ignored_pts, offset);
760 761
                } else {
                    // Composition
762
                    int in = getItemPosition(current_clipId) - offset;
763 764 765
                    ignored_pts.push_back(in);
                    ignored_pts.push_back(in + getItemPlaytime(current_clipId));
                }
766
            }
767
        }
768
        int snapped = getBestSnapPos(currentPos, position - currentPos, ignored_pts, cursorPosition, snapDistance);
Nicolas Carion's avatar
Nicolas Carion committed
769
        // qDebug() << "Starting suggestion " << clipId << position << currentPos << "snapped to " << snapped;
770 771 772
        if (snapped >= 0) {
            position = snapped;
        }
773
    }
Nicolas Carion's avatar
Nicolas Carion committed
774
    // we check if move is possible
775
    bool possible = (m_editMode == TimelineMode::NormalEdit) ? requestClipMove(clipId, trackId, position, moveMirrorTracks, true, false, false)
776
                                                           : requestFakeClipMove(clipId, trackId, position, true, false, false);
777
    if (possible) {
778
        TRACE_RES(position);
779 780 781 782
        if (m_editMode != TimelineMode::NormalEdit) {
            trackId = m_allClips[clipId]->getFakeTrackId();
        }
        return {position, trackId};
783
    }
784
    if (sourceTrackId == -1) {
785
        // not clear what to do here, if the current move doesn't work. We could try to find empty space, but it might end up being far away...
786
        TRACE_RES(currentPos);
787
        return {currentPos, -1};
788
    }
789 790
    // Find best possible move
    if (!m_groups->isInGroup(clipId)) {
791
        // Try same track move
792
        if (trackId != sourceTrackId && sourceTrackId != -1) {
793
            trackId = sourceTrackId;
794
            possible = requestClipMove(clipId, trackId, position, moveMirrorTracks, true, false, false);
795 796 797
            if (!possible) {
                qDebug() << "CANNOT MOVE CLIP : " << clipId << " ON TK: " << trackId << ", AT POS: " << position;
            } else {
798
                TRACE_RES(position);
799
                return {position, trackId