diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp
index dab4cbbe8..9f19ba458 100644
--- a/src/timeline2/model/clipmodel.cpp
+++ b/src/timeline2/model/clipmodel.cpp
@@ -1,735 +1,734 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "clipmodel.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "clipsnapmodel.hpp"
#include "core.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "logger.hpp"
#include "macros.hpp"
#include "timelinemodel.hpp"
#include "trackmodel.hpp"
#include
#include
#include
#include
ClipModel::ClipModel(const std::shared_ptr &parent, std::shared_ptr prod, const QString &binClipId, int id,
PlaylistState::ClipState state, double speed)
: MoveableItem(parent, id)
, m_producer(std::move(prod))
, m_effectStack(EffectStackModel::construct(m_producer, {ObjectType::TimelineClip, m_id}, parent->m_undoStack))
, m_clipMarkerModel(new ClipSnapModel())
, m_binClipId(binClipId)
, forceThumbReload(false)
, m_currentState(state)
, m_speed(speed)
, m_fakeTrack(-1)
, m_positionOffset(0)
{
m_producer->set("kdenlive:id", binClipId.toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
m_canBeVideo = binClip->hasVideo();
m_canBeAudio = binClip->hasAudio();
m_clipType = binClip->clipType();
if (binClip) {
m_endlessResize = !binClip->hasLimitedDuration();
} else {
m_endlessResize = false;
}
QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, QVector roles) {
qDebug() << "// GOT CLIP STACK DATA CHANGE: " << roles;
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
qDebug() << "// GOT CLIP STACK DATA CHANGE DONE: " << ix << " = " << roles;
ptr->dataChanged(ix, ix, roles);
}
}
});
}
int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state, double speed)
{
id = (id == -1 ? TimelineModel::getNextId() : id);
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId);
// We refine the state according to what the clip can actually produce
std::pair videoAudio = stateToBool(state);
videoAudio.first = videoAudio.first && binClip->hasVideo();
videoAudio.second = videoAudio.second && binClip->hasAudio();
state = stateFromBool(videoAudio);
std::shared_ptr cutProducer = binClip->getTimelineProducer(-1, id, state, speed);
std::shared_ptr clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed));
TRACE_CONSTR(clip.get(), parent, binClipId, id, state, speed);
clip->setClipState_lambda(state)();
parent->registerClip(clip);
clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel());
return id;
}
int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, const std::shared_ptr &producer,
PlaylistState::ClipState state)
{
// we hand the producer to the bin clip, and in return we get a cut to a good master producer
// We might not be able to use directly the producer that we receive as an argument, because it cannot share the same master producer with any other
// clipModel (due to a mlt limitation, see ProjectClip doc)
int id = TimelineModel::getNextId();
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId);
// We refine the state according to what the clip can actually produce
std::pair videoAudio = stateToBool(state);
videoAudio.first = videoAudio.first && binClip->hasVideo();
videoAudio.second = videoAudio.second && binClip->hasAudio();
state = stateFromBool(videoAudio);
double speed = 1.0;
if (QString::fromUtf8(producer->parent().get("mlt_service")) == QLatin1String("timewarp")) {
speed = producer->parent().get_double("warp_speed");
}
auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state);
std::shared_ptr clip(new ClipModel(parent, result.first, binClipId, id, state, speed));
clip->setClipState_lambda(state)();
clip->m_effectStack->importEffects(producer, state, result.second);
parent->registerClip(clip);
clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel());
return id;
}
void ClipModel::registerClipToBin(std::shared_ptr service, bool registerProducer)
{
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
if (!binClip) {
qDebug() << "Error : Bin clip for id: " << m_binClipId << " NOT AVAILABLE!!!";
}
qDebug() << "REGISTRATION " << m_id << "ptr count" << m_parent.use_count();
binClip->registerService(m_parent, m_id, std::move(service), registerProducer);
}
void ClipModel::deregisterClipToBin()
{
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
binClip->deregisterTimelineClip(m_id);
}
ClipModel::~ClipModel() = default;
bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo)
{
QWriteLocker locker(&m_lock);
// qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" <<
// m_producer->get_length();
if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) {
return false;
}
int delta = getPlaytime() - size;
if (delta == 0) {
return true;
}
int in = m_producer->get_in();
int out = m_producer->get_out();
int old_in = in, old_out = out;
// check if there is enough space on the chosen side
if (!right && in + delta < 0 && !m_endlessResize) {
return false;
}
if (!m_endlessResize && right && (out - delta >= m_producer->get_length())) {
return false;
}
if (right) {
out -= delta;
} else {
in += delta;
}
// qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
std::function track_operation = []() { return true; };
std::function track_reverse = []() { return true; };
int outPoint = out;
int inPoint = in;
int offset = 0;
if (m_endlessResize) {
offset = inPoint;
outPoint = out - in;
inPoint = 0;
}
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right);
} else {
qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
Q_ASSERT(false);
}
} else {
// Ensure producer is long enough
if (m_endlessResize && outPoint > m_producer->parent().get_length()) {
m_producer->set("length", outPoint + 1);
}
}
Fun operation = [this, inPoint, outPoint, track_operation]() {
if (track_operation()) {
setInOut(inPoint, outPoint);
return true;
}
return false;
};
if (operation()) {
// Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here
if (m_currentTrackId != -1) {
QVector roles{TimelineModel::DurationRole};
if (!right) {
roles.push_back(TimelineModel::StartRole);
roles.push_back(TimelineModel::InPointRole);
} else {
roles.push_back(TimelineModel::OutPointRole);
}
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
// TODO: integrate in undo
ptr->dataChanged(ix, ix, roles);
track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right);
}
}
Fun reverse = [this, old_in, old_out, track_reverse]() {
if (track_reverse()) {
setInOut(old_in, old_out);
return true;
}
return false;
};
qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
<< m_producer->get_playtime();
if (logUndo) {
adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo);
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
return false;
}
const QString ClipModel::getProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return QString::fromUtf8(service()->parent().get(name.toUtf8().constData()));
}
return QString::fromUtf8(service()->get(name.toUtf8().constData()));
}
int ClipModel::getIntProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return service()->parent().get_int(name.toUtf8().constData());
}
return service()->get_int(name.toUtf8().constData());
}
QSize ClipModel::getFrameSize() const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return QSize(service()->parent().get_int("meta.media.width"), service()->parent().get_int("meta.media.height"));
}
return {service()->get_int("meta.media.width"), service()->get_int("meta.media.height")};
}
double ClipModel::getDoubleProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return service()->parent().get_double(name.toUtf8().constData());
}
return service()->get_double(name.toUtf8().constData());
}
Mlt::Producer *ClipModel::service() const
{
READ_LOCK();
return m_producer.get();
}
std::shared_ptr ClipModel::getProducer()
{
READ_LOCK();
return m_producer;
}
int ClipModel::getPlaytime() const
{
READ_LOCK();
return m_producer->get_playtime();
}
void ClipModel::setTimelineEffectsEnabled(bool enabled)
{
QWriteLocker locker(&m_lock);
m_effectStack->setEffectStackEnabled(enabled);
}
bool ClipModel::addEffect(const QString &effectId)
{
QWriteLocker locker(&m_lock);
if (EffectsRepository::get()->getType(effectId) == EffectType::Audio) {
if (m_currentState == PlaylistState::VideoOnly) {
return false;
}
} else if (m_currentState == PlaylistState::AudioOnly) {
return false;
}
m_effectStack->appendEffect(effectId);
return true;
}
bool ClipModel::copyEffect(const std::shared_ptr &stackModel, int rowId)
{
QWriteLocker locker(&m_lock);
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), m_currentState);
return true;
}
bool ClipModel::importEffects(std::shared_ptr stackModel)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(std::move(stackModel), m_currentState);
return true;
}
bool ClipModel::importEffects(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(std::move(service), m_currentState);
return true;
}
bool ClipModel::removeFade(bool fromStart)
{
QWriteLocker locker(&m_lock);
m_effectStack->removeFade(fromStart);
return true;
}
bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo)
{
QWriteLocker locker(&m_lock);
return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, offset, undo, redo, logUndo);
}
bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
qDebug() << ".... ADJUSTING FADE LENGTH: " << duration << " / " << effectName;
Fun operation = [this, duration, effectName, originalDuration]() {
return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(),
!isAudioOnly(), originalDuration > 0);
};
if (operation() && originalDuration > 0) {
Fun reverse = [this, originalDuration, effectName]() {
return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"),
audioEnabled(), !isAudioOnly(), true);
};
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return true;
}
bool ClipModel::audioEnabled() const
{
READ_LOCK();
return stateToBool(m_currentState).second;
}
bool ClipModel::isAudioOnly() const
{
READ_LOCK();
return m_currentState == PlaylistState::AudioOnly;
}
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed)
{
// We require that the producer is not in the track when we refresh the producer, because otherwise the modification will not be propagated. Remove the clip
// first, refresh, and then replant.
QWriteLocker locker(&m_lock);
int in = getIn();
int out = getOut();
- qDebug() << "refresh " << speed << m_speed << in << out;
if (!qFuzzyCompare(speed, m_speed) && !qFuzzyCompare(speed, 0.)) {
in = in * std::abs(m_speed / speed);
out = in + getPlaytime() - 1;
// prevent going out of the clip's range
out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1);
m_speed = speed;
qDebug() << "changing speed" << in << out << m_speed;
}
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
std::shared_ptr binProducer = binClip->getTimelineProducer(m_currentTrackId, m_id, state, m_speed);
m_producer = std::move(binProducer);
m_producer->set_in_and_out(in, out);
// replant effect stack in updated service
m_effectStack->resetService(m_producer);
m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
m_endlessResize = !binClip->hasLimitedDuration();
}
void ClipModel::refreshProducerFromBin()
{
refreshProducerFromBin(m_currentState);
}
bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
{
if (m_endlessResize) {
// no timewarp for endless producers
return false;
}
if (qFuzzyCompare(speed, m_speed)) {
// nothing to do
return true;
}
std::function local_undo = []() { return true; };
std::function local_redo = []() { return true; };
double previousSpeed = getSpeed();
int oldDuration = getPlaytime();
int newDuration = int(double(oldDuration) * std::abs(previousSpeed / speed));
int oldOut = getOut();
int oldIn = getIn();
auto operation = useTimewarpProducer_lambda(speed);
auto reverse = useTimewarpProducer_lambda(previousSpeed);
if (oldOut >= newDuration) {
// in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer
reverse = [reverse, oldIn, oldOut, this]() {
bool res = reverse();
if (res) {
setInOut(oldIn, oldOut);
}
return res;
};
}
if (operation()) {
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
bool res = requestResize(newDuration, true, local_undo, local_redo, true);
if (!res) {
local_undo();
return false;
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
qDebug() << "tw: operation fail";
return false;
}
Fun ClipModel::useTimewarpProducer_lambda(double speed)
{
QWriteLocker locker(&m_lock);
return [speed, this]() {
qDebug() << "timeWarp producer" << speed;
refreshProducerFromBin(m_currentState, speed);
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->notifyChange(ix, ix, TimelineModel::SpeedRole);
}
return true;
};
}
QVariant ClipModel::getAudioWaveform()
{
READ_LOCK();
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
if (binClip) {
return QVariant::fromValue(binClip->audioFrameCache);
}
return QVariant();
}
const QString &ClipModel::binId() const
{
return m_binClipId;
}
std::shared_ptr ClipModel::getMarkerModel() const
{
READ_LOCK();
return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel();
}
int ClipModel::audioChannels() const
{
READ_LOCK();
return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels();
}
int ClipModel::fadeIn() const
{
return m_effectStack->getFadePosition(true);
}
int ClipModel::fadeOut() const
{
return m_effectStack->getFadePosition(false);
}
double ClipModel::getSpeed() const
{
return m_speed;
}
KeyframeModel *ClipModel::getKeyframeModel()
{
return m_effectStack->getEffectKeyframeModel();
}
bool ClipModel::showKeyframes() const
{
READ_LOCK();
return !service()->get_int("kdenlive:hide_keyframes");
}
void ClipModel::setShowKeyframes(bool show)
{
QWriteLocker locker(&m_lock);
service()->set("kdenlive:hide_keyframes", (int)!show);
}
void ClipModel::setPosition(int pos)
{
MoveableItem::setPosition(pos);
m_clipMarkerModel->updateSnapModelPos(pos);
}
void ClipModel::setInOut(int in, int out)
{
MoveableItem::setInOut(in, out);
m_clipMarkerModel->updateSnapModelInOut(std::pair(in, out));
}
void ClipModel::setCurrentTrackId(int tid, bool finalMove)
{
if (tid == m_currentTrackId) {
return;
}
bool registerSnap = m_currentTrackId == -1 && tid > -1;
if (m_currentTrackId > -1 && tid == -1) {
// Removing clip
m_clipMarkerModel->deregisterSnapModel();
}
MoveableItem::setCurrentTrackId(tid, finalMove);
if (registerSnap) {
if (auto ptr = m_parent.lock()) {
m_clipMarkerModel->registerSnapModel(ptr->m_snaps, getPosition(), getIn(), getOut());
}
}
if (finalMove && tid != -1) {
refreshProducerFromBin(m_currentState);
}
}
Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state)
{
QWriteLocker locker(&m_lock);
return [this, state]() {
if (auto ptr = m_parent.lock()) {
m_currentState = state;
if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case
refreshProducerFromBin(m_currentState);
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::StatusRole});
}
return true;
}
return false;
};
}
bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo)
{
if (state == PlaylistState::VideoOnly && !canBeVideo()) {
return false;
}
if (state == PlaylistState::AudioOnly && !canBeAudio()) {
return false;
}
if (state == m_currentState) {
return true;
}
auto old_state = m_currentState;
auto operation = setClipState_lambda(state);
if (operation()) {
auto reverse = setClipState_lambda(old_state);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
return false;
}
PlaylistState::ClipState ClipModel::clipState() const
{
READ_LOCK();
return m_currentState;
}
ClipType::ProducerType ClipModel::clipType() const
{
READ_LOCK();
return m_clipType;
}
void ClipModel::passTimelineProperties(const std::shared_ptr &other)
{
READ_LOCK();
Mlt::Properties source(m_producer->get_properties());
Mlt::Properties dest(other->service()->get_properties());
dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect");
}
bool ClipModel::canBeVideo() const
{
return m_canBeVideo;
}
bool ClipModel::canBeAudio() const
{
return m_canBeAudio;
}
const QString ClipModel::effectNames() const
{
READ_LOCK();
return m_effectStack->effectNames();
}
int ClipModel::getFakeTrackId() const
{
return m_fakeTrack;
}
void ClipModel::setFakeTrackId(int fid)
{
m_fakeTrack = fid;
}
int ClipModel::getFakePosition() const
{
return m_fakePosition;
}
void ClipModel::setFakePosition(int fid)
{
m_fakePosition = fid;
}
QDomElement ClipModel::toXml(QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("clip"));
container.setAttribute(QStringLiteral("binid"), m_binClipId);
container.setAttribute(QStringLiteral("id"), m_id);
container.setAttribute(QStringLiteral("in"), getIn());
container.setAttribute(QStringLiteral("out"), getOut());
container.setAttribute(QStringLiteral("position"), getPosition());
container.setAttribute(QStringLiteral("state"), (int)m_currentState);
if (auto ptr = m_parent.lock()) {
int trackId = ptr->getTrackPosition(m_currentTrackId);
container.setAttribute(QStringLiteral("track"), trackId);
if (ptr->isAudioTrack(getCurrentTrackId())) {
container.setAttribute(QStringLiteral("audioTrack"), 1);
int mirrorId = ptr->getMirrorVideoTrackId(m_currentTrackId);
if (mirrorId > -1) {
mirrorId = ptr->getTrackPosition(mirrorId);
}
container.setAttribute(QStringLiteral("mirrorTrack"), mirrorId);
}
}
container.setAttribute(QStringLiteral("speed"), m_speed);
container.appendChild(m_effectStack->toXml(document));
return container;
}
bool ClipModel::checkConsistency()
{
if (!m_effectStack->checkConsistency()) {
qDebug() << "Consistency check failed for effecstack";
return false;
}
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
auto instances = binClip->timelineInstances();
bool found = false;
for (const auto &i : instances) {
if (i == m_id) {
found = true;
break;
}
}
if (!found) {
qDebug() << "ERROR: binClip doesn't acknowledge timeline clip existence";
return false;
}
if (m_currentState == PlaylistState::VideoOnly && !m_canBeVideo) {
qDebug() << "ERROR: clip is in video state but doesn't have video";
return false;
}
if (m_currentState == PlaylistState::AudioOnly && !m_canBeAudio) {
qDebug() << "ERROR: clip is in video state but doesn't have video";
return false;
}
// TODO: check speed
return true;
}
int ClipModel::getSubPlaylistIndex() const
{
return m_subPlaylistIndex;
}
void ClipModel::setSubPlaylistIndex(int index)
{
m_subPlaylistIndex = index;
}
void ClipModel::setOffset(int offset)
{
m_positionOffset = offset;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::PositionOffsetRole});
}
}
void ClipModel::clearOffset()
{
if (m_positionOffset != 0) {
setOffset(0);
}
}
int ClipModel::getOffset() const
{
return m_positionOffset;
}
diff --git a/tests/modeltest.cpp b/tests/modeltest.cpp
index 18babb660..2e683d63b 100644
--- a/tests/modeltest.cpp
+++ b/tests/modeltest.cpp
@@ -1,1854 +1,1856 @@
#include "test_utils.hpp"
using namespace fakeit;
std::default_random_engine g(42);
Mlt::Profile profile_model;
TEST_CASE("Basic creation/deletion of a track", "[TrackModel]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(&profile_model, undoStack);
Mock timMock(tim);
auto timeline = std::shared_ptr(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
Fake(Method(timMock, adjustAssetRange));
// This is faked to allow to count calls
Fake(Method(timMock, _resetView));
- int id1 = TrackModel::construct(timeline);
+ int id1, id2, id3;
+ REQUIRE(timeline->requestTrackInsertion(-1, id1));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 1);
REQUIRE(timeline->getTrackPosition(id1) == 0);
// In the current implementation, when a track is added/removed, the model is notified with _resetView
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
- int id2 = TrackModel::construct(timeline);
+ REQUIRE(timeline->requestTrackInsertion(-1, id2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 2);
REQUIRE(timeline->getTrackPosition(id2) == 1);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
- int id3 = TrackModel::construct(timeline);
+ REQUIRE(timeline->requestTrackInsertion(-1, id3));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 3);
REQUIRE(timeline->getTrackPosition(id3) == 2);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
int id4;
REQUIRE(timeline->requestTrackInsertion(1, id4));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 4);
REQUIRE(timeline->getTrackPosition(id1) == 0);
REQUIRE(timeline->getTrackPosition(id4) == 1);
REQUIRE(timeline->getTrackPosition(id2) == 2);
REQUIRE(timeline->getTrackPosition(id3) == 3);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
// Test deletion
REQUIRE(timeline->requestTrackDeletion(id3));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 3);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
REQUIRE(timeline->requestTrackDeletion(id1));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 2);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
REQUIRE(timeline->requestTrackDeletion(id4));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 1);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
REQUIRE(timeline->requestTrackDeletion(id2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTracksCount() == 0);
Verify(Method(timMock, _resetView)).Exactly(Once);
RESET(timMock);
SECTION("Delete a track with groups")
{
int tid1, tid2;
REQUIRE(timeline->requestTrackInsertion(-1, tid1));
REQUIRE(timeline->requestTrackInsertion(-1, tid2));
REQUIRE(timeline->checkConsistency());
QString binId = createProducer(profile_model, "red", binModel);
int length = 20;
int cid1, cid2, cid3, cid4;
REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
REQUIRE(timeline->requestClipInsertion(binId, tid2, 0, cid2));
REQUIRE(timeline->requestClipInsertion(binId, tid2, length, cid3));
REQUIRE(timeline->requestClipInsertion(binId, tid2, 2 * length, cid4));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipsCount() == 4);
REQUIRE(timeline->getTracksCount() == 2);
auto g1 = std::unordered_set({cid1, cid3});
auto g2 = std::unordered_set({cid2, cid4});
auto g3 = std::unordered_set({cid1, cid4});
REQUIRE(timeline->requestClipsGroup(g1));
REQUIRE(timeline->requestClipsGroup(g2));
REQUIRE(timeline->requestClipsGroup(g3));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->requestTrackDeletion(tid1));
REQUIRE(timeline->getClipsCount() == 3);
REQUIRE(timeline->getTracksCount() == 1);
REQUIRE(timeline->checkConsistency());
}
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Basic creation/deletion of a clip", "[ClipModel]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
std::shared_ptr timeline = TimelineItemModel::construct(&profile_model, guideModel, undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
QString binId = createProducer(profile_model, "red", binModel);
QString binId2 = createProducer(profile_model, "green", binModel);
REQUIRE(timeline->getClipsCount() == 0);
int id1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
REQUIRE(timeline->getClipsCount() == 1);
REQUIRE(timeline->checkConsistency());
int id2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
REQUIRE(timeline->getClipsCount() == 2);
REQUIRE(timeline->checkConsistency());
int id3 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
REQUIRE(timeline->getClipsCount() == 3);
REQUIRE(timeline->checkConsistency());
// Test deletion
REQUIRE(timeline->requestItemDeletion(id2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipsCount() == 2);
REQUIRE(timeline->requestItemDeletion(id3));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipsCount() == 1);
REQUIRE(timeline->requestItemDeletion(id1));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipsCount() == 0);
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Clip manipulation", "[ClipModel]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(&profile_model, undoStack);
Mock timMock(tim);
auto timeline = std::shared_ptr(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
Fake(Method(timMock, adjustAssetRange));
// This is faked to allow to count calls
Fake(Method(timMock, _resetView));
Fake(Method(timMock, _beginInsertRows));
Fake(Method(timMock, _beginRemoveRows));
Fake(Method(timMock, _endInsertRows));
Fake(Method(timMock, _endRemoveRows));
QString binId = createProducer(profile_model, "red", binModel);
QString binId2 = createProducer(profile_model, "blue", binModel);
QString binId3 = createProducer(profile_model, "green", binModel);
int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
- int tid1 = TrackModel::construct(timeline);
- int tid2 = TrackModel::construct(timeline);
- int tid3 = TrackModel::construct(timeline);
+ int tid1, tid2, tid3;
+ REQUIRE(timeline->requestTrackInsertion(-1, tid1));
+ REQUIRE(timeline->requestTrackInsertion(-1, tid2));
+ REQUIRE(timeline->requestTrackInsertion(-1, tid3));
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
int cid3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly);
int cid4 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
Verify(Method(timMock, _resetView)).Exactly(3_Times);
RESET(timMock);
// for testing purposes, we make sure the clip will behave as regular clips
// (ie their size is fixed, we cannot resize them past their original size)
timeline->m_allClips[cid1]->m_endlessResize = false;
timeline->m_allClips[cid2]->m_endlessResize = false;
timeline->m_allClips[cid3]->m_endlessResize = false;
timeline->m_allClips[cid4]->m_endlessResize = false;
SECTION("Endless clips can be resized both sides")
{
timeline->m_allClips[cid1]->m_endlessResize = true;
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
int l = timeline->getClipPlaytime(cid1);
// try resizing uninserted clip
REQUIRE(timeline->requestItemResize(cid1, l + 2, false) == l + 2);
REQUIRE(timeline->getClipPlaytime(cid1) == l + 2);
undoStack->undo();
REQUIRE(timeline->getClipPlaytime(cid1) == l);
undoStack->redo();
REQUIRE(timeline->getClipPlaytime(cid1) == l + 2);
undoStack->undo();
REQUIRE(timeline->getClipPlaytime(cid1) == l);
REQUIRE(timeline->requestItemResize(cid1, 3 * l, true) == 3 * l);
REQUIRE(timeline->getClipPlaytime(cid1) == 3 * l);
undoStack->undo();
REQUIRE(timeline->getClipPlaytime(cid1) == l);
undoStack->redo();
REQUIRE(timeline->getClipPlaytime(cid1) == 3 * l);
undoStack->undo();
REQUIRE(timeline->getClipPlaytime(cid1) == l);
// try resizing inserted clip
int pos = 10;
REQUIRE(timeline->requestClipMove(cid1, tid1, pos));
auto state = [&](int s, int p) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == p);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
REQUIRE(timeline->getClipPlaytime(cid1) == s);
};
state(l, pos);
// too big
REQUIRE(timeline->requestItemResize(cid1, l + pos + 2, false) == -1);
REQUIRE(timeline->requestItemResize(cid1, l + 2, false) == l + 2);
state(l + 2, pos - 2);
undoStack->undo();
state(l, pos);
undoStack->redo();
state(l + 2, pos - 2);
undoStack->undo();
state(l, pos);
REQUIRE(timeline->requestItemResize(cid1, 3 * l, true) == 3 * l);
state(3 * l, pos);
undoStack->undo();
state(l, pos);
undoStack->redo();
state(3 * l, pos);
undoStack->undo();
state(l, pos);
}
SECTION("Insert a clip in a track and change track")
{
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
REQUIRE(timeline->getClipTrackId(cid1) == -1);
REQUIRE(timeline->getClipPosition(cid1) == -1);
int pos = 10;
REQUIRE(timeline->requestClipMove(cid1, tid1, pos));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
// Check that the model was correctly notified
CHECK_INSERT(Once);
pos = 1;
REQUIRE(timeline->requestClipMove(cid1, tid2, pos));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
CHECK_MOVE(Once);
// Check conflicts
int pos2 = binModel->getClipByBinID(binId)->frameDuration();
REQUIRE(timeline->requestClipMove(cid2, tid1, pos2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
CHECK_INSERT(Once);
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 + 2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
CHECK_MOVE(Once);
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 - 2));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
CHECK_MOVE(Once);
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
CHECK_MOVE(Once);
}
int length = binModel->getClipByBinID(binId)->frameDuration();
SECTION("Insert consecutive clips")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
CHECK_INSERT(Once);
REQUIRE(timeline->requestClipMove(cid2, tid1, length));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == length);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
CHECK_INSERT(Once);
}
SECTION("Resize orphan clip")
{
REQUIRE(timeline->getClipPlaytime(cid2) == length);
REQUIRE(timeline->requestItemResize(cid2, 5, true) == 5);
REQUIRE(timeline->checkConsistency());
REQUIRE(binModel->getClipByBinID(binId)->frameDuration() == length);
auto inOut = std::pair{0, 4};
REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut);
REQUIRE(timeline->getClipPlaytime(cid2) == 5);
REQUIRE(timeline->requestItemResize(cid2, 10, false) == -1);
REQUIRE(timeline->requestItemResize(cid2, length + 1, true) == -1);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipPlaytime(cid2) == 5);
REQUIRE(timeline->getClipPlaytime(cid2) == 5);
REQUIRE(timeline->requestItemResize(cid2, 2, false) == 2);
REQUIRE(timeline->checkConsistency());
inOut = std::pair{3, 4};
REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut);
REQUIRE(timeline->getClipPlaytime(cid2) == 2);
REQUIRE(timeline->requestItemResize(cid2, length, true) == -1);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipPlaytime(cid2) == 2);
CAPTURE(timeline->m_allClips[cid2]->m_producer->get_in());
REQUIRE(timeline->requestItemResize(cid2, length - 2, true) == -1);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->requestItemResize(cid2, length - 3, true) == length - 3);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipPlaytime(cid2) == length - 3);
}
SECTION("Resize inserted clips")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
CHECK_INSERT(Once);
REQUIRE(timeline->requestItemResize(cid1, 5, true) == 5);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipPlaytime(cid1) == 5);
REQUIRE(timeline->getClipPosition(cid1) == 0);
CHECK_RESIZE(Once);
REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
REQUIRE(timeline->checkConsistency());
REQUIRE(binModel->getClipByBinID(binId)->frameDuration() == length);
CHECK_INSERT(Once);
REQUIRE(timeline->requestItemResize(cid1, 6, true) == -1);
REQUIRE(timeline->requestItemResize(cid1, 6, false) == -1);
REQUIRE(timeline->checkConsistency());
NO_OTHERS();
REQUIRE(timeline->requestItemResize(cid2, length - 5, false) == length - 5);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipPosition(cid2) == 10);
CHECK_RESIZE(Once);
REQUIRE(timeline->requestItemResize(cid1, 10, true) == 10);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
CHECK_RESIZE(Once);
}
SECTION("Change track of resized clips")
{
// // REQUIRE(timeline->allowClipMove(cid2, tid1, 5));
REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
// // REQUIRE(timeline->allowClipMove(cid1, tid2, 10));
REQUIRE(timeline->requestClipMove(cid1, tid2, 10));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->requestItemResize(cid1, 5, false) == 5);
REQUIRE(timeline->checkConsistency());
// // REQUIRE(timeline->allowClipMove(cid1, tid1, 0));
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
}
SECTION("Clip Move")
{
REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == 5);
REQUIRE(timeline->requestClipMove(cid1, tid1, 5 + length));
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5 + length);
REQUIRE(timeline->getClipPosition(cid2) == 5);
};
state();
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 3 + length));
state();
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
state();
REQUIRE(timeline->requestClipMove(cid2, tid1, 0));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5 + length);
REQUIRE(timeline->getClipPosition(cid2) == 0);
};
state2();
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
state2();
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, length - 5));
state2();
REQUIRE(timeline->requestClipMove(cid1, tid1, length));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length);
REQUIRE(timeline->getClipPosition(cid2) == 0);
REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length);
REQUIRE(timeline->getClipPosition(cid2) == 0);
REQUIRE(timeline->requestClipMove(cid1, tid1, length - 5));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length - 5);
REQUIRE(timeline->getClipPosition(cid2) == 0);
REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length - 5);
REQUIRE(timeline->getClipPosition(cid2) == 5);
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length - 5);
REQUIRE(timeline->getClipPosition(cid2) == 5);
REQUIRE(timeline->requestClipMove(cid2, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == length - 5);
REQUIRE(timeline->getClipPosition(cid2) == 0);
}
SECTION("Move and resize")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->requestItemResize(cid1, length - 2, false) == length - 2);
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPlaytime(cid1) == length - 2);
};
state();
// try to resize past the left end
REQUIRE(timeline->requestItemResize(cid1, length, false) == -1);
state();
REQUIRE(timeline->requestItemResize(cid1, length - 4, true) == length - 4);
REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1));
REQUIRE(timeline->requestItemResize(cid2, length - 2, false) == length - 2);
REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPlaytime(cid1) == length - 4);
REQUIRE(timeline->getClipPosition(cid2) == length - 4 + 1);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 2);
};
state2();
// the gap between the two clips is 1 frame, we try to resize them by 2 frames
REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1);
state2();
REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
state2();
REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4));
auto state3 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPlaytime(cid1) == length - 4);
REQUIRE(timeline->getClipPosition(cid2) == length - 4);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 2);
};
state3();
// Now the gap is 0 frames, the resize should still fail
REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1);
state3();
REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
state3();
// We move cid1 out of the way
REQUIRE(timeline->requestClipMove(cid1, tid2, 0));
// now resize should work
REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == length - 2);
REQUIRE(timeline->requestItemResize(cid2, length, false) == length);
REQUIRE(timeline->checkConsistency());
}
SECTION("Group and selection")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->requestClipMove(cid2, tid1, length + 3));
REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 5));
auto pos_state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPosition(cid2) == length + 3);
REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
};
auto state0 = [&]() {
pos_state();
REQUIRE_FALSE(timeline->m_groups->isInGroup(cid1));
REQUIRE_FALSE(timeline->m_groups->isInGroup(cid2));
REQUIRE_FALSE(timeline->m_groups->isInGroup(cid3));
};
state0();
REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
auto state = [&]() {
pos_state();
REQUIRE_FALSE(timeline->m_groups->isInGroup(cid3));
REQUIRE(timeline->m_groups->isInGroup(cid1));
int gid = timeline->m_groups->getRootId(cid1);
REQUIRE(timeline->m_groups->getLeaves(gid) == std::unordered_set{cid1, cid2});
};
state();
// undo/redo should work fine
undoStack->undo();
state0();
undoStack->redo();
state();
// Tricky case, we do a non-trivial selection before undoing
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid1, cid2, cid3});
undoStack->undo();
state0();
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid1, cid3});
undoStack->redo();
state();
// same thing, but when ungrouping manually
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid1, cid2, cid3});
REQUIRE(timeline->requestClipUngroup(cid1));
state0();
// normal undo/redo
undoStack->undo();
state();
undoStack->redo();
state0();
// undo/redo mixed with selections
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid1, cid3});
undoStack->undo();
state();
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid1, cid2, cid3});
undoStack->redo();
state0();
}
SECTION("Group move")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->requestClipMove(cid2, tid1, length + 3));
REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 5));
REQUIRE(timeline->requestClipMove(cid4, tid2, 4));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipTrackId(cid4) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPosition(cid2) == length + 3);
REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
REQUIRE(timeline->getClipPosition(cid4) == 4);
// check that move is possible without groups
REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 3));
REQUIRE(timeline->checkConsistency());
undoStack->undo();
REQUIRE(timeline->checkConsistency());
// check that move is possible without groups
REQUIRE(timeline->requestClipMove(cid4, tid2, 9));
REQUIRE(timeline->checkConsistency());
undoStack->undo();
REQUIRE(timeline->checkConsistency());
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipTrackId(cid4) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPosition(cid2) == length + 3);
REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
REQUIRE(timeline->getClipPosition(cid4) == 4);
};
state();
// grouping
REQUIRE(timeline->requestClipsGroup({cid1, cid3}));
REQUIRE(timeline->requestClipsGroup({cid1, cid4}));
// move left is now forbidden, because clip1 is at position 0
REQUIRE_FALSE(timeline->requestClipMove(cid3, tid1, 2 * length + 3));
state();
// this move is impossible, because clip1 runs into clip2
REQUIRE_FALSE(timeline->requestClipMove(cid4, tid2, 9));
state();
// this move is possible
REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 8));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid3) == 0);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipTrackId(cid4) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == 3);
REQUIRE(timeline->getClipPosition(cid2) == length + 3);
REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 8);
REQUIRE(timeline->getClipPosition(cid4) == 7);
};
state1();
// this move is possible
REQUIRE(timeline->requestClipMove(cid1, tid2, 8));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getTrackClipsCount(tid2) == 2);
REQUIRE(timeline->getTrackClipsCount(tid3) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid2);
REQUIRE(timeline->getClipTrackId(cid4) == tid3);
REQUIRE(timeline->getClipPosition(cid1) == 8);
REQUIRE(timeline->getClipPosition(cid2) == length + 3);
REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5 + 8);
REQUIRE(timeline->getClipPosition(cid4) == 4 + 8);
};
state2();
undoStack->undo();
state1();
undoStack->redo();
state2();
REQUIRE(timeline->requestClipMove(cid1, tid1, 3));
state1();
}
SECTION("Group move consecutive clips")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 7));
REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + length));
REQUIRE(timeline->requestClipMove(cid3, tid1, 7 + 2 * length));
REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 3 * length));
REQUIRE(timeline->requestClipsGroup({cid1, cid2, cid3, cid4}));
auto state = [&](int tid, int start) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid) == 4);
int i = 0;
for (int cid : std::vector({cid1, cid2, cid3, cid4})) {
REQUIRE(timeline->getClipTrackId(cid) == tid);
REQUIRE(timeline->getClipPosition(cid) == start + i * length);
REQUIRE(timeline->getClipPlaytime(cid) == length);
i++;
}
};
state(tid1, 7);
auto check_undo = [&](int target, int tid, int oldTid) {
state(tid, target);
undoStack->undo();
state(oldTid, 7);
undoStack->redo();
state(tid, target);
undoStack->undo();
state(oldTid, 7);
};
REQUIRE(timeline->requestClipMove(cid1, tid1, 6));
qDebug() << "state1";
state(tid1, 6);
undoStack->undo();
state(tid1, 7);
undoStack->redo();
state(tid1, 6);
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
qDebug() << "state2";
state(tid1, 0);
undoStack->undo();
state(tid1, 6);
undoStack->redo();
state(tid1, 0);
undoStack->undo();
state(tid1, 6);
undoStack->undo();
state(tid1, 7);
REQUIRE(timeline->requestClipMove(cid3, tid1, 1 + 2 * length));
qDebug() << "state3";
check_undo(1, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid4, tid1, 4 + 3 * length));
qDebug() << "state4";
check_undo(4, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid4, tid1, 11 + 3 * length));
qDebug() << "state5";
check_undo(11, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid2, tid1, 13 + length));
qDebug() << "state6";
check_undo(13, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid1, tid1, 20));
qDebug() << "state7";
check_undo(20, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 4 * length));
qDebug() << "state8";
check_undo(length + 7, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + 2 * length));
qDebug() << "state9";
check_undo(length + 7, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid1, tid1, 7 + length));
qDebug() << "state10";
check_undo(length + 7, tid1, tid1);
REQUIRE(timeline->requestClipMove(cid2, tid2, 8 + length));
qDebug() << "state11";
check_undo(8, tid2, tid1);
}
SECTION("Group move to unavailable track")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 10));
REQUIRE(timeline->requestClipMove(cid2, tid2, 12));
REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == 10);
REQUIRE(timeline->getClipPosition(cid2) == 12);
};
state();
REQUIRE_FALSE(timeline->requestClipMove(cid2, tid1, 10));
state();
REQUIRE_FALSE(timeline->requestClipMove(cid2, tid1, 100));
state();
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid3, 100));
state();
}
SECTION("Group move with non-consecutive track ids")
{
int tid5 = TrackModel::construct(timeline);
int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
Q_UNUSED(cid6);
int tid6 = TrackModel::construct(timeline);
REQUIRE(tid5 + 1 != tid6);
REQUIRE(timeline->requestClipMove(cid1, tid5, 10));
REQUIRE(timeline->requestClipMove(cid2, tid5, length + 10));
REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
auto state = [&](int t) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(t) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == t);
REQUIRE(timeline->getClipTrackId(cid2) == t);
REQUIRE(timeline->getClipPosition(cid1) == 10);
REQUIRE(timeline->getClipPosition(cid2) == 10 + length);
};
state(tid5);
REQUIRE(timeline->requestClipMove(cid1, tid6, 10));
state(tid6);
}
SECTION("Creation and movement of AV groups")
{
int tid6b = TrackModel::construct(timeline, -1, -1, QString(), true);
int tid6 = TrackModel::construct(timeline, -1, -1, QString(), true);
int tid5 = TrackModel::construct(timeline);
int tid5b = TrackModel::construct(timeline);
auto state0 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid5) == 0);
REQUIRE(timeline->getTrackClipsCount(tid6) == 0);
};
state0();
QString binId3 = createProducerWithSound(profile_model, binModel);
int cid6 = -1;
REQUIRE(timeline->requestClipInsertion(binId3, tid5, 3, cid6, true, true, false));
int cid7 = timeline->m_groups->getSplitPartner(cid6);
auto check_group = [&]() {
// we check that the av group was correctly created
REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set({cid6, cid7}));
int g1 = timeline->m_groups->getDirectAncestor(cid6);
REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({cid6, cid7}));
REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit);
};
auto state = [&](int pos) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid5) == 1);
REQUIRE(timeline->getTrackClipsCount(tid6) == 1);
REQUIRE(timeline->getClipTrackId(cid6) == tid5);
REQUIRE(timeline->getClipTrackId(cid7) == tid6);
REQUIRE(timeline->getClipPosition(cid6) == pos);
REQUIRE(timeline->getClipPosition(cid7) == pos);
REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly);
REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly);
check_group();
};
state(3);
undoStack->undo();
state0();
undoStack->redo();
state(3);
// test deletion + undo after selection
REQUIRE(timeline->requestSetSelection({cid6}));
REQUIRE(timeline->getCurrentSelection() == std::unordered_set{cid6, cid7});
REQUIRE(timeline->requestItemDeletion(cid6, true));
state0();
undoStack->undo();
state(3);
undoStack->redo();
state0();
undoStack->undo();
state(3);
// simple translation on the right
REQUIRE(timeline->requestClipMove(cid6, tid5, 10, true, true, true));
state(10);
undoStack->undo();
state(3);
undoStack->redo();
state(10);
// simple translation on the left, moving the audio clip this time
REQUIRE(timeline->requestClipMove(cid7, tid6, 1, true, true, true));
state(1);
undoStack->undo();
state(10);
undoStack->redo();
state(1);
// change track, moving video
REQUIRE(timeline->requestClipMove(cid6, tid5b, 7, true, true, true));
auto state2 = [&](int pos) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid5b) == 1);
REQUIRE(timeline->getTrackClipsCount(tid6b) == 1);
REQUIRE(timeline->getClipTrackId(cid6) == tid5b);
REQUIRE(timeline->getClipTrackId(cid7) == tid6b);
REQUIRE(timeline->getClipPosition(cid6) == pos);
REQUIRE(timeline->getClipPosition(cid7) == pos);
REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly);
REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly);
check_group();
};
state2(7);
undoStack->undo();
state(1);
undoStack->redo();
state2(7);
// change track, moving audio
REQUIRE(timeline->requestClipMove(cid7, tid6b, 2, true, true, true));
state2(2);
undoStack->undo();
state2(7);
undoStack->redo();
state2(2);
undoStack->undo();
undoStack->undo();
state(1);
}
SECTION("Clip clone")
{
int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
int l = timeline->getClipPlaytime(cid6);
REQUIRE(timeline->requestItemResize(cid6, l - 3, true, true, -1) == l - 3);
REQUIRE(timeline->requestItemResize(cid6, l - 7, false, true, -1) == l - 7);
int newId;
std::function undo = []() { return true; };
std::function redo = []() { return true; };
REQUIRE(TimelineFunctions::cloneClip(timeline, cid6, newId, PlaylistState::VideoOnly, undo, redo));
REQUIRE(timeline->m_allClips[cid6]->binId() == timeline->m_allClips[newId]->binId());
// TODO check effects
}
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Check id unicity", "[ClipModel]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(&profile_model, undoStack);
Mock timMock(tim);
auto timeline = std::shared_ptr(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
RESET(timMock);
QString binId = createProducer(profile_model, "red", binModel);
std::vector track_ids;
std::unordered_set all_ids;
std::bernoulli_distribution coin(0.5);
const int nbr = 20;
for (int i = 0; i < nbr; i++) {
if (coin(g)) {
int tid = TrackModel::construct(timeline);
REQUIRE(all_ids.count(tid) == 0);
all_ids.insert(tid);
track_ids.push_back(tid);
REQUIRE(timeline->getTracksCount() == track_ids.size());
} else {
int cid = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
REQUIRE(all_ids.count(cid) == 0);
all_ids.insert(cid);
REQUIRE(timeline->getClipsCount() == all_ids.size() - track_ids.size());
}
}
REQUIRE(timeline->checkConsistency());
REQUIRE(all_ids.size() == nbr);
REQUIRE(all_ids.size() != track_ids.size());
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Undo and Redo", "[ClipModel]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(&profile_model, undoStack);
Mock timMock(tim);
auto timeline = std::shared_ptr(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
RESET(timMock);
QString binId = createProducer(profile_model, "red", binModel);
QString binId2 = createProducer(profile_model, "blue", binModel);
int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
int tid1 = TrackModel::construct(timeline);
int tid2 = TrackModel::construct(timeline);
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
timeline->m_allClips[cid1]->m_endlessResize = false;
timeline->m_allClips[cid2]->m_endlessResize = false;
int length = 20;
int nclips = timeline->m_allClips.size();
SECTION("requestCreateClip")
{
// an invalid clip id shouldn't get created
{
int temp;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE_FALSE(timeline->requestClipCreation("impossible bin id", temp, PlaylistState::VideoOnly, 1., undo, redo));
}
auto state0 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips);
};
state0();
QString binId3 = createProducer(profile_model, "green", binModel);
int cid3;
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE(timeline->requestClipCreation(binId3, cid3, PlaylistState::VideoOnly, 1., undo, redo));
pCore->pushUndo(undo, redo, QString());
}
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips + 1);
REQUIRE(timeline->getClipPlaytime(cid3) == length);
REQUIRE(timeline->getClipTrackId(cid3) == -1);
};
state1();
QString binId4 = binId3 + "/1/10";
int cid4;
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE(timeline->requestClipCreation(binId4, cid4, PlaylistState::VideoOnly, 1., undo, redo));
pCore->pushUndo(undo, redo, QString());
}
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips + 2);
REQUIRE(timeline->getClipPlaytime(cid4) == 10);
REQUIRE(timeline->getClipTrackId(cid4) == -1);
auto inOut = std::pair({1, 10});
REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut);
REQUIRE(timeline->getClipPlaytime(cid3) == length);
REQUIRE(timeline->getClipTrackId(cid3) == -1);
};
state2();
undoStack->undo();
state1();
undoStack->undo();
state0();
undoStack->redo();
state1();
undoStack->redo();
state2();
}
SECTION("requestInsertClip")
{
auto state0 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips);
};
state0();
QString binId3 = createProducer(profile_model, "green", binModel);
int cid3;
REQUIRE(timeline->requestClipInsertion(binId3, tid1, 12, cid3, true));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips + 1);
REQUIRE(timeline->getClipPlaytime(cid3) == length);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid3) == 12);
};
state1();
QString binId4 = binId3 + "/1/10";
int cid4;
REQUIRE(timeline->requestClipInsertion(binId4, tid2, 17, cid4, true));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->m_allClips.size() == nclips + 2);
REQUIRE(timeline->getClipPlaytime(cid4) == 10);
REQUIRE(timeline->getClipTrackId(cid4) == tid2);
REQUIRE(timeline->getClipPosition(cid4) == 17);
auto inOut = std::pair({1, 10});
REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut);
REQUIRE(timeline->getClipPlaytime(cid3) == length);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid3) == 12);
};
state2();
undoStack->undo();
state1();
undoStack->undo();
state0();
undoStack->redo();
state1();
undoStack->redo();
state2();
}
int init_index = undoStack->index();
SECTION("Basic move undo")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
CHECK_INSERT(Once);
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(undoStack->index() == init_index + 2);
// Move on same track does not trigger insert/remove row
CHECK_MOVE(0);
undoStack->undo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
CHECK_MOVE(0);
undoStack->redo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(undoStack->index() == init_index + 2);
CHECK_MOVE(0);
undoStack->undo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
CHECK_MOVE(0);
REQUIRE(timeline->requestClipMove(cid1, tid1, 2 * length));
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 2 * length);
REQUIRE(undoStack->index() == init_index + 2);
CHECK_MOVE(0);
undoStack->undo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
CHECK_MOVE(0);
undoStack->redo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 2 * length);
REQUIRE(undoStack->index() == init_index + 2);
CHECK_MOVE(0);
undoStack->undo();
CHECK_MOVE(0);
undoStack->undo();
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getClipTrackId(cid1) == -1);
REQUIRE(undoStack->index() == init_index);
CHECK_REMOVE(Once);
}
SECTION("Basic resize orphan clip undo")
{
REQUIRE(timeline->getClipPlaytime(cid2) == length);
REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
REQUIRE(undoStack->index() == init_index + 1);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
REQUIRE(undoStack->index() == init_index + 2);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
REQUIRE(undoStack->index() == init_index + 2);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
undoStack->undo();
REQUIRE(undoStack->index() == init_index + 1);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
undoStack->redo();
REQUIRE(undoStack->index() == init_index + 2);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
undoStack->undo();
REQUIRE(undoStack->index() == init_index + 1);
REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
undoStack->undo();
REQUIRE(undoStack->index() == init_index);
REQUIRE(timeline->getClipPlaytime(cid2) == length);
}
SECTION("Basic resize inserted clip undo")
{
REQUIRE(timeline->getClipPlaytime(cid2) == length);
auto check = [&](int pos, int l) {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPlaytime(cid2) == l);
REQUIRE(timeline->getClipPosition(cid2) == pos);
};
REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
INFO("Test 1");
check(5, length);
REQUIRE(undoStack->index() == init_index + 1);
REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
INFO("Test 2");
check(5, length - 5);
REQUIRE(undoStack->index() == init_index + 2);
REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
INFO("Test 3");
check(10, length - 10);
REQUIRE(undoStack->index() == init_index + 3);
REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
INFO("Test 4");
check(10, length - 10);
REQUIRE(undoStack->index() == init_index + 3);
undoStack->undo();
INFO("Test 5");
check(5, length - 5);
REQUIRE(undoStack->index() == init_index + 2);
undoStack->redo();
INFO("Test 6");
check(10, length - 10);
REQUIRE(undoStack->index() == init_index + 3);
undoStack->undo();
INFO("Test 7");
check(5, length - 5);
REQUIRE(undoStack->index() == init_index + 2);
undoStack->undo();
INFO("Test 8");
check(5, length);
REQUIRE(undoStack->index() == init_index + 1);
}
SECTION("Clip Insertion Undo")
{
QString binId3 = createProducer(profile_model, "red", binModel);
REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
};
state1();
int cid3;
REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 5, cid3));
state1();
REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 6, cid3));
state1();
REQUIRE(timeline->requestClipInsertion(binId3, tid1, 5 + length, cid3));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(timeline->getClipPosition(cid3) == 5 + length);
REQUIRE(timeline->m_allClips[cid3]->isValid());
REQUIRE(undoStack->index() == init_index + 2);
};
state2();
REQUIRE(timeline->requestClipMove(cid3, tid1, 10 + length));
auto state3 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(timeline->getClipPosition(cid3) == 10 + length);
REQUIRE(undoStack->index() == init_index + 3);
};
state3();
REQUIRE(timeline->requestItemResize(cid3, 1, true) == 1);
auto state4 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid3) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(timeline->getClipPlaytime(cid3) == 1);
REQUIRE(timeline->getClipPosition(cid3) == 10 + length);
REQUIRE(undoStack->index() == init_index + 4);
};
state4();
undoStack->undo();
state3();
undoStack->undo();
state2();
undoStack->undo();
state1();
undoStack->redo();
state2();
undoStack->redo();
state3();
undoStack->redo();
state4();
undoStack->undo();
state3();
undoStack->undo();
state2();
undoStack->undo();
state1();
}
SECTION("Clip Deletion undo")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
};
state1();
int nbClips = timeline->getClipsCount();
REQUIRE(timeline->requestItemDeletion(cid1));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getClipsCount() == nbClips - 1);
REQUIRE(undoStack->index() == init_index + 2);
};
state2();
undoStack->undo();
state1();
undoStack->redo();
state2();
undoStack->undo();
state1();
}
SECTION("Select then delete")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
REQUIRE(timeline->requestClipMove(cid2, tid2, 1));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getClipTrackId(cid2) == tid2);
REQUIRE(timeline->getClipPosition(cid2) == 1);
};
state1();
REQUIRE(timeline->requestSetSelection({cid1, cid2}));
int nbClips = timeline->getClipsCount();
REQUIRE(timeline->requestItemDeletion(cid1));
auto state2 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
REQUIRE(timeline->getClipsCount() == nbClips - 2);
};
state2();
undoStack->undo();
state1();
undoStack->redo();
state2();
undoStack->undo();
state1();
}
SECTION("Track insertion undo")
{
std::map orig_trackPositions, final_trackPositions;
for (const auto &it : timeline->m_iteratorTable) {
int track = it.first;
int pos = timeline->getTrackPosition(track);
orig_trackPositions[track] = pos;
if (pos >= 1) pos++;
final_trackPositions[track] = pos;
}
auto checkPositions = [&](const std::map &pos) {
for (const auto &p : pos) {
REQUIRE(timeline->getTrackPosition(p.first) == p.second);
}
};
checkPositions(orig_trackPositions);
int new_tid;
REQUIRE(timeline->requestTrackInsertion(1, new_tid));
checkPositions(final_trackPositions);
undoStack->undo();
checkPositions(orig_trackPositions);
undoStack->redo();
checkPositions(final_trackPositions);
undoStack->undo();
checkPositions(orig_trackPositions);
}
SECTION("Track deletion undo")
{
int nb_clips = timeline->getClipsCount();
int nb_tracks = timeline->getTracksCount();
REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
auto state1 = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 5);
REQUIRE(undoStack->index() == init_index + 1);
REQUIRE(timeline->getClipsCount() == nb_clips);
REQUIRE(timeline->getTracksCount() == nb_tracks);
};
state1();
REQUIRE(timeline->requestTrackDeletion(tid1));
REQUIRE(timeline->getClipsCount() == nb_clips - 1);
REQUIRE(timeline->getTracksCount() == nb_tracks - 1);
undoStack->undo();
state1();
undoStack->redo();
REQUIRE(timeline->getClipsCount() == nb_clips - 1);
REQUIRE(timeline->getTracksCount() == nb_tracks - 1);
undoStack->undo();
state1();
}
int clipCount = timeline->m_allClips.size();
SECTION("Clip creation and resize")
{
int cid6;
auto state0 = [&]() {
REQUIRE(timeline->m_allClips.size() == clipCount);
REQUIRE(timeline->checkConsistency());
};
state0();
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
REQUIRE(timeline->requestClipCreation(binId, cid6, PlaylistState::VideoOnly, 1., undo, redo));
pCore->pushUndo(undo, redo, QString());
}
int l = timeline->getClipPlaytime(cid6);
auto state1 = [&]() {
REQUIRE(timeline->m_allClips.size() == clipCount + 1);
REQUIRE(timeline->isClip(cid6));
REQUIRE(timeline->getClipTrackId(cid6) == -1);
REQUIRE(timeline->getClipPlaytime(cid6) == l);
};
state1();
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
REQUIRE(timeline->requestItemResize(cid6, l - 5, true, true, undo, redo, false));
pCore->pushUndo(undo, redo, QString());
}
auto state2 = [&]() {
REQUIRE(timeline->m_allClips.size() == clipCount + 1);
REQUIRE(timeline->isClip(cid6));
REQUIRE(timeline->getClipTrackId(cid6) == -1);
REQUIRE(timeline->getClipPlaytime(cid6) == l - 5);
};
state2();
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
REQUIRE(timeline->requestClipMove(cid6, tid1, 7, true, true, true, undo, redo));
pCore->pushUndo(undo, redo, QString());
}
auto state3 = [&]() {
REQUIRE(timeline->m_allClips.size() == clipCount + 1);
REQUIRE(timeline->isClip(cid6));
REQUIRE(timeline->getClipTrackId(cid6) == tid1);
REQUIRE(timeline->getClipPosition(cid6) == 7);
REQUIRE(timeline->getClipPlaytime(cid6) == l - 5);
};
state3();
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
REQUIRE(timeline->requestItemResize(cid6, l - 6, false, true, undo, redo, false));
pCore->pushUndo(undo, redo, QString());
}
auto state4 = [&]() {
REQUIRE(timeline->m_allClips.size() == clipCount + 1);
REQUIRE(timeline->isClip(cid6));
REQUIRE(timeline->getClipTrackId(cid6) == tid1);
REQUIRE(timeline->getClipPosition(cid6) == 8);
REQUIRE(timeline->getClipPlaytime(cid6) == l - 6);
};
state4();
undoStack->undo();
state3();
undoStack->undo();
state2();
undoStack->undo();
state1();
undoStack->undo();
state0();
undoStack->redo();
state1();
undoStack->redo();
state2();
undoStack->redo();
state3();
undoStack->redo();
state4();
}
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Snapping", "[Snapping]")
{
Logger::clear();
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(&profile_model, undoStack);
Mock timMock(tim);
auto timeline = std::shared_ptr(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
RESET(timMock);
QString binId = createProducer(profile_model, "red", binModel, 50);
QString binId2 = createProducer(profile_model, "blue", binModel);
int tid1 = TrackModel::construct(timeline);
int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
int tid2 = TrackModel::construct(timeline);
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
int cid3 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
timeline->m_allClips[cid1]->m_endlessResize = false;
timeline->m_allClips[cid2]->m_endlessResize = false;
timeline->m_allClips[cid3]->m_endlessResize = false;
int length = timeline->getClipPlaytime(cid1);
int length2 = timeline->getClipPlaytime(cid2);
SECTION("getBlankSizeNearClip")
{
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
// before
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 0);
// after
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
REQUIRE(timeline->requestClipMove(cid1, tid1, 10));
// before
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
// after
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
REQUIRE(timeline->requestClipMove(cid2, tid1, 25 + length));
// before
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 15);
// after
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 15);
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX);
REQUIRE(timeline->requestClipMove(cid2, tid1, 10 + length));
// before
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 0);
// after
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 0);
REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX);
}
SECTION("Snap move to a single clip")
{
int beg = 30;
// in the absence of other clips, a valid move shouldn't be modified
for (int snap = -1; snap <= 5; ++snap) {
REQUIRE(timeline->suggestClipMove(cid2, tid2, beg, -1, snap) == beg);
REQUIRE(timeline->suggestClipMove(cid2, tid2, beg + length, -1, snap) == beg + length);
REQUIRE(timeline->checkConsistency());
}
// We add a clip in first track to create snap points
REQUIRE(timeline->requestClipMove(cid1, tid1, beg));
// Now a clip in second track should snap to beginning
auto check_snap = [&](int pos, int perturb, int snap) {
if (snap >= perturb) {
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap) == pos);
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap) == pos);
} else {
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap) == pos + perturb);
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap) == pos - perturb);
}
};
for (int snap = -1; snap <= 5; ++snap) {
for (int perturb = 0; perturb <= 6; ++perturb) {
// snap to beginning
check_snap(beg, perturb, snap);
check_snap(beg + length, perturb, snap);
// snap to end
check_snap(beg - length2, perturb, snap);
check_snap(beg + length - length2, perturb, snap);
REQUIRE(timeline->checkConsistency());
}
}
// Same test, but now clip is moved in position 0 first
REQUIRE(timeline->requestClipMove(cid2, tid2, 0));
for (int snap = -1; snap <= 5; ++snap) {
for (int perturb = 0; perturb <= 6; ++perturb) {
// snap to beginning
check_snap(beg, perturb, snap);
check_snap(beg + length, perturb, snap);
// snap to end
check_snap(beg - length2, perturb, snap);
check_snap(beg + length - length2, perturb, snap);
REQUIRE(timeline->checkConsistency());
}
}
}
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}