timelinemodel.cpp 187 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, QMap <int, int> moving_clips)
572
{
Vincent Pinon's avatar
Vincent Pinon committed
573
    Q_UNUSED(moveMirrorTracks)
574
    //qDebug() << "//\n\nRQST CLIP MOVE\n\n//\n// FINAL MOVE: "<<finalMove<<", PLAYLIST: "<<m_allClips[clipId]->getSubPlaylistIndex();
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 596 597 598
    int previous_track = moving_clips.value(clipId, -1);
    if (old_trackId == -1) {
        //old_trackId = previous_track;
    }
599
    bool notifyViewOnly = false;
600
    // qDebug()<<"MOVING CLIP FROM: "<<old_trackId<<" == "<<trackId;
601 602 603
    Fun update_model = []() { return true; };
    if (old_trackId == trackId) {
        // Move on same track, simply inform the view
Nicolas Carion's avatar
Nicolas Carion committed
604
        updateView = false;
605
        notifyViewOnly = true;
606
        update_model = [clipId, this, trackId, invalidateTimeline]() {
607
            QModelIndex modelIndex = makeClipIndexFromID(clipId);
Nicolas Carion's avatar
Nicolas Carion committed
608
            notifyChange(modelIndex, modelIndex, StartRole);
609
            if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
610 611 612
                int in = getClipPosition(clipId);
                emit invalidateZone(in, in + getClipPlaytime(clipId));
            }
613 614 615
            return true;
        };
    }
616 617 618 619 620
    Fun sync_mix = []() { return true; };
    Fun update_playlist = []() { return true; };
    Fun update_playlist_undo = []() { return true; };
    Fun simple_move_mix = []() { return true; };
    Fun simple_restore_mix = []() { return true; };
621
    if (old_trackId == -1 && isTrack(previous_track) && getTrackById_const(previous_track)->hasMix(clipId) && previous_track != trackId) {
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
        // Clip is moved to another track
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(previous_track)->getMixInfo(clipId);
        bool mixGroupMove = false;
        if (m_groups->isInGroup(clipId)) {
            int parentGroup = m_groups->getRootId(clipId);
            if (parentGroup > -1) {
                std::unordered_set<int> sub = m_groups->getLeaves(parentGroup);
                if (sub.count(mixData.first.firstClipId) > 0 && sub.count(mixData.first.secondClipId) > 0) {
                    mixGroupMove = true;
                }
            }
        }
        if (mixGroupMove) {
            // We are moving a group on another track, delete and re-add
            bool isAudio = getTrackById_const(previous_track)->isAudioTrack();
            simple_move_mix = [this, previous_track, trackId, clipId, finalMove, mixData, isAudio]() {
                getTrackById_const(previous_track)->syncronizeMixes(finalMove);
                bool result = getTrackById_const(trackId)->createMix(mixData.first, isAudio);
                return result;
            };
            simple_restore_mix = [this, previous_track, trackId, finalMove, mixData, isAudio]() {
                bool result = true;
                if (finalMove) {
                    result = getTrackById_const(previous_track)->createMix(mixData.first, isAudio);
                    getTrackById_const(trackId)->syncronizeMixes(finalMove);
                    //getTrackById_const(previous_track)->syncronizeMixes(finalMove);
                }
                return result;
            };
        }
652
    } else if (finalMove && !groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId)) {
653
        // Clip has a mix
654 655
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
        if (mixData.first.firstClipId > -1) {
656
            // We have a mix at clip start
657 658
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
659 660
                return true;
            };
661 662 663 664 665
            if (old_trackId == trackId) {
                // We are moving a group on same track
                if (finalMove && position >= mixData.first.firstClipInOut.second) {
                    int subPlaylist = m_allClips[clipId]->getSubPlaylistIndex();
                    update_playlist = [this, clipId, subPlaylist]() {
666
                        m_allClips[clipId]->setSubPlaylistIndex(subPlaylist == 0 ? 1 : 0);
667 668 669 670 671 672 673 674 675
                        return true;
                    };
                    bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                    update_playlist_undo = [this, clipId, subPlaylist, old_trackId, mixData, isAudio]() {
                        m_allClips[clipId]->setSubPlaylistIndex(subPlaylist);
                        bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                        return result;
                    };
                }
676
            } else if (finalMove) {
677 678
                // Clip moved to another track, delete mix
                int subPlaylist = m_allClips[clipId]->getSubPlaylistIndex();
679
                update_playlist = [this, clipId, old_trackId, finalMove]() {
680
                    m_allClips[clipId]->setMixDuration(0);
681
                    m_allClips[clipId]->setSubPlaylistIndex(0);
682 683 684 685 686 687 688 689 690
                    getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
                    return true;
                };
                bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                update_playlist_undo = [this, clipId, subPlaylist, mixData, old_trackId, isAudio, finalMove]() {
                    m_allClips[clipId]->setSubPlaylistIndex(subPlaylist);
                    bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                    return result;
                };
691
            }
692
        }
693 694
        if (mixData.second.firstClipId > -1) {
            // We have a mix at clip end
695 696 697
            int clipDuration = mixData.second.firstClipInOut.second - mixData.second.firstClipInOut.first;
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
698 699
                return true;
            };
700 701
            if (old_trackId == trackId) {
                if (finalMove && (position + clipDuration <= mixData.second.secondClipInOut.first)) {
702 703
                    // Moved outside mix zone
                    bool switchPlaylist = !getTrackById_const(trackId)->hasStartMix(clipId);
704
                    int subPlaylist = m_allClips[mixData.second.secondClipId]->getSubPlaylistIndex();
705 706 707 708 709 710
                    if (switchPlaylist) {
                        update_playlist = [this, mixData, subPlaylist, trackId]() {
                            bool result = getTrackById_const(trackId)->switchPlaylist(mixData.second.secondClipId, mixData.second.secondClipInOut.first, subPlaylist, 0);
                            return result;
                        };
                    }
711
                    bool isAudio = getTrackById_const(trackId)->isAudioTrack();
712 713 714 715 716
                    update_playlist_undo = [this, mixData, subPlaylist, trackId, isAudio, switchPlaylist]() {
                        bool result = true;
                        if (switchPlaylist) {
                            result = getTrackById_const(trackId)->switchPlaylist(mixData.second.secondClipId, mixData.second.secondClipInOut.first, 0, subPlaylist);
                        }
717 718 719 720
                        result = result && getTrackById_const(trackId)->createMix(mixData.second, isAudio);
                        return result;
                    };
                }
721 722 723 724 725 726 727 728 729 730 731
            } else {
                // Clip moved to another track, delete mix
                // Mix will be deleted by syncronizeMixes operation, only
                // re-add it on undo
                bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                    bool result = getTrackById_const(old_trackId)->createMix(mixData.second, isAudio);
                    return result;
                };
            }
        }
732
    } else if (finalMove && groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId) && old_trackId == trackId) {
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
        if (mixData.first.firstClipId > -1) {
            // Mix on clip start, check if mix is still in range
            if (!moving_clips.contains(mixData.first.firstClipId)) {
                int mixEnd = m_allClips[mixData.first.firstClipId]->getPosition() + m_allClips[mixData.first.firstClipId]->getPlaytime();
                if (mixEnd < position) {
                    // Mix will be deleted, recreate on undo
                    bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                    update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                        bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                        return result;
                    };
                }
            }
        }
        if (mixData.second.firstClipId > -1) {
            // Mix on clip end, check if mix is still in range
            if (!moving_clips.contains(mixData.second.secondClipId)) {
                int mixEnd = m_allClips[mixData.second.secondClipId]->getPosition();
                if (mixEnd > position + m_allClips[clipId]->getPlaytime()) {
                    // Mix will be deleted, recreate on undo
                    bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                    update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                        bool result = getTrackById_const(old_trackId)->createMix(mixData.second, isAudio);
                        return result;
                    };
                }
760 761
            }
        }
762
    }
763 764 765 766
    PUSH_LAMBDA(simple_restore_mix, local_undo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_undo);
    }
767
    if (old_trackId != -1) {
768 769 770
        if (notifyViewOnly) {
            PUSH_LAMBDA(update_model, local_undo);
        }
771
        ok = getTrackById(old_trackId)->requestClipDeletion(clipId, updateView, finalMove, local_undo, local_redo, groupMove, false);
772
        if (!ok) {
773 774
            bool undone = local_undo();
            Q_ASSERT(undone);
775 776 777
            return false;
        }
    }
778 779
    update_playlist();
    UPDATE_UNDO_REDO(update_playlist, update_playlist_undo, local_undo, local_redo);
780
    ok = getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove);
781
    if (!ok) {
782
        qDebug() << "-------------\n\nINSERTION FAILED, REVERTING\n\n-------------------";
783 784
        bool undone = local_undo();
        Q_ASSERT(undone);
785
        return false;
786
    }
787
    sync_mix();
788
    update_model();
789 790 791 792 793
    simple_move_mix();
    PUSH_LAMBDA(simple_move_mix, local_redo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_redo);
    }
794 795 796
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
Nicolas Carion's avatar
Nicolas Carion committed
797 798
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return true;
799 800
}

801 802 803
bool TimelineModel::mixClip(int idToMove)
{
    int selectedTrack = -1;
804 805
    std::unordered_set<int> initialSelection = getCurrentSelection();
    if (idToMove == -1 && initialSelection.empty()) {
806 807 808 809
        pCore->displayMessage(i18n("Select a clip to apply the mix"), InformationMessage, 500);
        return false;
    }
    std::pair<int, int> clipsToMix;
810
    int mixPosition = 0;
811
    int previousClip = -1;
812 813 814 815 816 817 818
    if (idToMove != -1) {
        initialSelection = {idToMove};
        idToMove = -1;
    }
    for (int s : initialSelection) {
        if (!isClip(s)) {
            continue;
819
        }
820 821 822
        selectedTrack = getClipTrackId(s);
        if (selectedTrack == -1 || !isTrack(selectedTrack)) {
            continue;
823
        }
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
        mixPosition = getItemPosition(s);
        int clipDuration =  getItemPlaytime(s);    
        // Check if we have a clip before and/or after
        int nextClip = -1;
        // Check if clip already has a mix
        if (getTrackById_const(selectedTrack)->hasStartMix(s)) {
            if (getTrackById_const(selectedTrack)->hasEndMix(s)) {
                continue;
            }
            nextClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition + clipDuration + 1);
        } else if (getTrackById_const(selectedTrack)->hasEndMix(s)) {
            previousClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition - 1);
            if (previousClip > -1 && getTrackById_const(selectedTrack)->hasEndMix(previousClip)) {
                // Could happen if 2 clips before are mixed to full length
                previousClip = -1;
            }
840
        } else {
841 842
            previousClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition - 1);
            nextClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition + clipDuration + 1);
843
        }
844 845 846 847 848 849 850 851 852 853 854
        if (previousClip > -1 && nextClip > -1) {
            // We have a clip before and a clip after, check timeline cursor position to decide where to mix
            int cursor = pCore->getTimelinePosition();
            if (cursor < mixPosition + clipDuration / 2) {
                nextClip = -1;
            } else {
                previousClip = -1;
            }
        }
        if (nextClip == -1) {
            if (previousClip == -1) {
855
            // No clip to mix, abort
856 857 858 859 860 861 862 863 864 865 866 867 868 869
                continue;
            }
            // Mix at start of selected clip
            clipsToMix.first = previousClip;
            clipsToMix.second = s;
            idToMove = s;
            break;
        } else {
            // Mix at end of selected clip
            mixPosition += clipDuration;
            clipsToMix.first = s;
            clipsToMix.second = nextClip;
            idToMove = s;
            break;
870 871
        }
    }
872 873 874 875 876 877 878
    
    if (idToMove == -1 || !isClip(idToMove)) {
        pCore->displayMessage(i18n("Select a clip to apply the mix"), InformationMessage, 500);
        return false;
    }

    
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
    bool result = requestClipMix(clipsToMix, selectedTrack, mixPosition, true, true, true, undo,
 redo, false);
    if (result) {
        // Check if this is an AV split group
        if (m_groups->isInGroup(idToMove)) {
            int parentGroup = m_groups->getRootId(idToMove);
            if (parentGroup > -1 && m_groups->getType(parentGroup) == GroupType::AVSplit) {
                std::unordered_set<int> sub = m_groups->getLeaves(