mirror of
https://invent.kde.org/multimedia/kdenlive
synced 2025-12-05 15:59:59 +01:00
2241 lines
90 KiB
C++
2241 lines
90 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2019-2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
|
|
SPDX-FileCopyrightText: 2017-2019 Nicolas Carion <french.ebook.lover@gmail.com>
|
|
SPDX-FileCopyrightText: 2022 Eric Jiang
|
|
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
*/
|
|
#include "test_utils.hpp"
|
|
// test specific headers
|
|
#include "doc/kdenlivedoc.h"
|
|
#include <QUndoGroup>
|
|
|
|
using namespace fakeit;
|
|
std::default_random_engine g(42);
|
|
|
|
TEST_CASE("Basic creation/deletion of a track", "[TrackModel]")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
KdenliveDoc document(undoStack);
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
Fake(Method(timMock, adjustAssetRange));
|
|
|
|
// This is faked to allow to count calls
|
|
|
|
int id1, id2, id3;
|
|
REQUIRE(timeline->requestTrackInsertion(-1, id1));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 1);
|
|
REQUIRE(timeline->getTrackPosition(id1) == 0);
|
|
RESET(timMock);
|
|
|
|
REQUIRE(timeline->requestTrackInsertion(-1, id2));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 2);
|
|
REQUIRE(timeline->getTrackPosition(id2) == 1);
|
|
RESET(timMock);
|
|
|
|
REQUIRE(timeline->requestTrackInsertion(-1, id3));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 3);
|
|
REQUIRE(timeline->getTrackPosition(id3) == 2);
|
|
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);
|
|
RESET(timMock);
|
|
|
|
// Test deletion
|
|
REQUIRE(timeline->requestTrackDeletion(id3));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 3);
|
|
RESET(timMock);
|
|
|
|
REQUIRE(timeline->requestTrackDeletion(id1));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 2);
|
|
RESET(timMock);
|
|
|
|
REQUIRE(timeline->requestTrackDeletion(id4));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 1);
|
|
RESET(timMock);
|
|
|
|
// We are not allowed to delete the last track
|
|
REQUIRE_FALSE(timeline->requestTrackDeletion(id2));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTracksCount() == 1);
|
|
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 = KdenliveTests::createProducer(pCore->getProjectProfile(), "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() == 3);
|
|
|
|
auto g1 = std::unordered_set<int>({cid1, cid3});
|
|
auto g2 = std::unordered_set<int>({cid2, cid4});
|
|
auto g3 = std::unordered_set<int>({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() == 2);
|
|
REQUIRE(timeline->checkConsistency());
|
|
}
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Adding multiple A/V tracks", "[TrackModel]")
|
|
{
|
|
|
|
auto binModel = pCore->projectItemModel();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
KdenliveDoc document(undoStack);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
SECTION("Check AV track ordering")
|
|
{
|
|
// start state:
|
|
// * V2 (position 3)
|
|
// * V1
|
|
// * A1
|
|
// * A2 (position 0)
|
|
int a1, a2, v1, v2;
|
|
REQUIRE(timeline->requestTrackInsertion(0, a2, QString(), true));
|
|
REQUIRE(timeline->requestTrackInsertion(1, a1, QString(), true));
|
|
REQUIRE(timeline->requestTrackInsertion(2, v1, QString(), false));
|
|
REQUIRE(timeline->requestTrackInsertion(3, v2, QString(), false));
|
|
|
|
// when we add 3 AV tracks above V1, we should have:
|
|
// * V5 (position 9)
|
|
// * V4
|
|
// * V3
|
|
// * V2
|
|
// * V1
|
|
// * A1
|
|
// * A2
|
|
// * A3
|
|
// * A4
|
|
// * A5 (position 0)
|
|
QString trackName("New track");
|
|
REQUIRE(timeline->addTracksAtPosition(3, 3, trackName, false, true, false));
|
|
// but if the new tracks keep getting added at the same position 3, then we'll get
|
|
// * V2
|
|
// * V3
|
|
// * V1
|
|
// * V4
|
|
// * A4
|
|
// * V5
|
|
// * A5
|
|
// * A1
|
|
// * A3
|
|
// * A2
|
|
// (numbering doesn't look like this in the GUI)
|
|
|
|
REQUIRE(timeline->getTracksCount() == 10);
|
|
|
|
// first 5 tracks should be audio, and last 5 tracks should be video
|
|
int max = timeline->getTracksCount();
|
|
for (int ix = 0; ix < max; ix++) {
|
|
int tid = timeline->getTrackIndexFromPosition(ix);
|
|
auto trackModel = KdenliveTests::getTrackById_const(timeline, tid);
|
|
if (ix < 5) {
|
|
CHECK(trackModel->isAudioTrack());
|
|
} else {
|
|
CHECK(!(trackModel->isAudioTrack()));
|
|
}
|
|
}
|
|
// V1 track should be at index 5 (i.e. we shouldn't have inserted any video
|
|
// tracks before it)
|
|
REQUIRE(timeline->getTrackIndexFromPosition(5) == v1);
|
|
}
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Basic creation/deletion of a clip", "[ClipModel]")
|
|
{
|
|
|
|
auto binModel = pCore->projectItemModel();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel);
|
|
QString binId2 = KdenliveTests::createProducer(pCore->getProjectProfile(), "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);
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Clip manipulation", "[ClipModel]")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
Fake(Method(timMock, adjustAssetRange));
|
|
|
|
// This is faked to allow to count calls
|
|
Fake(Method(timMock, _beginInsertRows));
|
|
Fake(Method(timMock, _beginRemoveRows));
|
|
Fake(Method(timMock, _endInsertRows));
|
|
Fake(Method(timMock, _endRemoveRows));
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel);
|
|
QString binId2 = KdenliveTests::createProducer(pCore->getProjectProfile(), "blue", binModel);
|
|
QString binId3 = KdenliveTests::createProducer(pCore->getProjectProfile(), "green", binModel);
|
|
QString binId_unlimited = KdenliveTests::createProducer(pCore->getProjectProfile(), "green", binModel, 20, false);
|
|
|
|
int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
|
|
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);
|
|
int cid5 = ClipModel::construct(timeline, binId_unlimited, -1, PlaylistState::VideoOnly);
|
|
|
|
RESET(timMock);
|
|
|
|
SECTION("Endless clips can be resized both sides")
|
|
{
|
|
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
|
|
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
|
|
int l = timeline->getClipPlaytime(cid5);
|
|
|
|
// try resizing uninserted clip
|
|
REQUIRE(timeline->requestItemResize(cid5, l + 2, false) == l + 2);
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l + 2);
|
|
undoStack->undo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l);
|
|
undoStack->redo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l + 2);
|
|
undoStack->undo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l);
|
|
|
|
REQUIRE(timeline->requestItemResize(cid5, 3 * l, true) == 3 * l);
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == 3 * l);
|
|
undoStack->undo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l);
|
|
undoStack->redo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == 3 * l);
|
|
undoStack->undo();
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == l);
|
|
|
|
// try resizing inserted clip
|
|
int pos = 10;
|
|
REQUIRE(timeline->requestClipMove(cid5, tid1, pos));
|
|
|
|
auto state = [&](int s, int p) {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid5) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid5) == p);
|
|
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
|
|
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
|
|
REQUIRE(timeline->getClipPlaytime(cid5) == s);
|
|
};
|
|
state(l, pos);
|
|
|
|
// too big
|
|
REQUIRE(timeline->requestItemResize(cid5, l + pos + 2, false) == l + pos);
|
|
undoStack->undo();
|
|
|
|
REQUIRE(timeline->requestItemResize(cid5, 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(cid5, 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)->getFramePlaytime() == length);
|
|
auto inOut = std::pair<int, int>{0, 4};
|
|
REQUIRE(timeline->getClipInOut(cid2) == 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<int, int>{3, 4};
|
|
REQUIRE(timeline->getClipInOut(cid2) == 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)->getFramePlaytime() == length);
|
|
CHECK_INSERT(Once);
|
|
|
|
int currentLength = timeline->getClipPlaytime(cid1);
|
|
REQUIRE(timeline->requestItemResize(cid1, 6, true) == currentLength);
|
|
REQUIRE(timeline->requestItemResize(cid1, 6, false) == currentLength);
|
|
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
|
|
int currentLength = timeline->getClipPlaytime(cid1);
|
|
REQUIRE(timeline->requestItemResize(cid1, length, false) == currentLength);
|
|
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
|
|
// It will only be resized by one frame
|
|
currentLength = timeline->getClipPlaytime(cid1);
|
|
qDebug() << "::: TRYING TO RESIZE CLIP CURRENT: " << currentLength << " / RESIZE: " << (length - 2)
|
|
<< ", NEXT AT: " << timeline->getClipPosition(cid2);
|
|
REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == length - 3);
|
|
undoStack->undo();
|
|
state2();
|
|
// Resize a clip over another clip will resize it to fill the gap
|
|
REQUIRE(timeline->requestItemResize(cid2, length, false) == length - 1);
|
|
undoStack->undo();
|
|
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
|
|
currentLength = timeline->getClipPlaytime(cid1);
|
|
REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == currentLength);
|
|
state3();
|
|
currentLength = timeline->getClipPlaytime(cid2);
|
|
REQUIRE(timeline->requestItemResize(cid2, length, false) == currentLength);
|
|
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->isInGroup(cid1));
|
|
REQUIRE_FALSE(timeline->isInGroup(cid2));
|
|
REQUIRE_FALSE(timeline->isInGroup(cid3));
|
|
};
|
|
state0();
|
|
|
|
REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
|
|
auto state = [&]() {
|
|
pos_state();
|
|
REQUIRE_FALSE(timeline->isInGroup(cid3));
|
|
REQUIRE(timeline->isInGroup(cid1));
|
|
int gid = KdenliveTests::groupsModel(timeline)->getRootId(cid1);
|
|
REQUIRE(KdenliveTests::groupsModel(timeline)->getLeaves(gid) == std::unordered_set<int>{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<int>{cid1, cid2, cid3});
|
|
undoStack->undo();
|
|
state0();
|
|
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
|
|
REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid3});
|
|
undoStack->redo();
|
|
state();
|
|
|
|
// same thing, but when ungrouping manually
|
|
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
|
|
REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{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<int>{cid1, cid3});
|
|
undoStack->undo();
|
|
state();
|
|
REQUIRE(timeline->requestSetSelection({cid1, cid3}));
|
|
REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{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<int>({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);
|
|
};
|
|
state();
|
|
|
|
// Moving clips on an unavailable track will do a same track move
|
|
REQUIRE(timeline->requestClipMove(cid2, tid1, 10));
|
|
REQUIRE(timeline->getClipPosition(cid1) == 8);
|
|
REQUIRE(timeline->getClipPosition(cid2) == 10);
|
|
state();
|
|
REQUIRE(timeline->requestClipMove(cid2, tid1, 100));
|
|
REQUIRE(timeline->getClipPosition(cid1) == 98);
|
|
REQUIRE(timeline->getClipPosition(cid2) == 100);
|
|
state();
|
|
REQUIRE(timeline->requestClipMove(cid1, tid3, 100));
|
|
REQUIRE(timeline->getClipPosition(cid1) == 100);
|
|
REQUIRE(timeline->getClipPosition(cid2) == 102);
|
|
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 = KdenliveTests::createProducerWithSound(pCore->getProjectProfile(), binModel);
|
|
|
|
int cid6 = -1;
|
|
// Setup insert stream data
|
|
QMap<int, QString> audioInfo;
|
|
audioInfo.insert(1, QStringLiteral("stream1"));
|
|
KdenliveTests::setAudioTargets(timeline, audioInfo);
|
|
REQUIRE(timeline->requestClipInsertion(binId3, tid5, 3, cid6, true, true, false));
|
|
int cid7 = KdenliveTests::groupsModel(timeline)->getSplitPartner(cid6);
|
|
|
|
auto check_group = [&]() {
|
|
// we check that the av group was correctly created
|
|
REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set<int>({cid6, cid7}));
|
|
int g1 = KdenliveTests::groupsModel(timeline)->getDirectAncestor(cid6);
|
|
REQUIRE(KdenliveTests::groupsModel(timeline)->getDirectChildren(g1) == std::unordered_set<int>({cid6, cid7}));
|
|
REQUIRE(KdenliveTests::groupsModel(timeline)->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(KdenliveTests::getClipPtr(timeline, cid6)->clipState() == PlaylistState::VideoOnly);
|
|
REQUIRE(KdenliveTests::getClipPtr(timeline, 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<int>{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(KdenliveTests::getClipPtr(timeline, cid6)->clipState() == PlaylistState::VideoOnly);
|
|
REQUIRE(KdenliveTests::getClipPtr(timeline, 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<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
REQUIRE(TimelineFunctions::cloneClip(timeline, cid6, newId, PlaylistState::VideoOnly, -1, undo, redo));
|
|
REQUIRE(timeline->getClipBinId(cid6) == timeline->getClipBinId(newId));
|
|
// TODO check effects
|
|
}
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Check id unicity", "[ClipModel]")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
RESET(timMock);
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel);
|
|
|
|
std::vector<int> track_ids;
|
|
std::unordered_set<int> 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() == int(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() == int(all_ids.size() - track_ids.size()));
|
|
}
|
|
}
|
|
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(all_ids.size() == nbr);
|
|
REQUIRE(all_ids.size() != track_ids.size());
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Undo and Redo", "[ClipModel]")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
RESET(timMock);
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel);
|
|
QString binId2 = KdenliveTests::createProducer(pCore->getProjectProfile(), "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);
|
|
|
|
int length = 20;
|
|
int nclips = timeline->getClipsCount();
|
|
|
|
SECTION("requestCreateClip")
|
|
{
|
|
// an invalid clip id shouldn't get created
|
|
{
|
|
int temp;
|
|
Fun undo = []() { return true; };
|
|
Fun redo = []() { return true; };
|
|
REQUIRE_FALSE(KdenliveTests::requestClipCreation(timeline, "impossible bin id", temp, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
|
|
}
|
|
|
|
auto state0 = [&]() {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == nclips);
|
|
};
|
|
state0();
|
|
|
|
QString binId3 = KdenliveTests::createProducer(pCore->getProjectProfile(), "green", binModel);
|
|
int cid3;
|
|
{
|
|
Fun undo = []() { return true; };
|
|
Fun redo = []() { return true; };
|
|
REQUIRE(KdenliveTests::requestClipCreation(timeline, binId3, cid3, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
|
|
auto state1 = [&]() {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 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(KdenliveTests::requestClipCreation(timeline, binId4, cid4, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
|
|
auto state2 = [&]() {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == nclips + 2);
|
|
REQUIRE(timeline->getClipPlaytime(cid4) == 10);
|
|
REQUIRE(timeline->getClipTrackId(cid4) == -1);
|
|
auto inOut = std::pair<int, int>({1, 10});
|
|
REQUIRE(timeline->getClipInOut(cid4) == 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->getClipsCount() == nclips);
|
|
};
|
|
state0();
|
|
|
|
QString binId3 = KdenliveTests::createProducer(pCore->getProjectProfile(), "green", binModel);
|
|
int cid3;
|
|
REQUIRE(timeline->requestClipInsertion(binId3, tid1, 12, cid3, true));
|
|
|
|
auto state1 = [&]() {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 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->getClipsCount() == nclips + 2);
|
|
REQUIRE(timeline->getClipPlaytime(cid4) == 10);
|
|
REQUIRE(timeline->getClipTrackId(cid4) == tid2);
|
|
REQUIRE(timeline->getClipPosition(cid4) == 17);
|
|
auto inOut = std::pair<int, int>({1, 10});
|
|
REQUIRE(timeline->getClipInOut(cid4) == 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 = KdenliveTests::createProducer(pCore->getProjectProfile(), "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->clipIsValid(cid3));
|
|
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<int, int> orig_trackPositions, final_trackPositions;
|
|
|
|
int max = timeline->getTracksCount();
|
|
for (int ix = 0; ix < max; ix++) {
|
|
int tid = timeline->getTrackIndexFromPosition(ix);
|
|
int pos = ix;
|
|
orig_trackPositions[tid] = pos;
|
|
if (pos >= 1) pos++;
|
|
final_trackPositions[tid] = pos;
|
|
}
|
|
|
|
auto checkPositions = [&](const std::map<int, int> &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->getClipsCount();
|
|
SECTION("Clip creation and resize")
|
|
{
|
|
int cid6;
|
|
auto state0 = [&]() {
|
|
REQUIRE(timeline->getClipsCount() == clipCount);
|
|
REQUIRE(timeline->checkConsistency());
|
|
};
|
|
state0();
|
|
|
|
{
|
|
std::function<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
REQUIRE(KdenliveTests::requestClipCreation(timeline, binId, cid6, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
int l = timeline->getClipPlaytime(cid6);
|
|
|
|
auto state1 = [&]() {
|
|
REQUIRE(timeline->getClipsCount() == clipCount + 1);
|
|
REQUIRE(timeline->isClip(cid6));
|
|
REQUIRE(timeline->getClipTrackId(cid6) == -1);
|
|
REQUIRE(timeline->getClipPlaytime(cid6) == l);
|
|
};
|
|
state1();
|
|
|
|
{
|
|
std::function<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
int size = l - 5;
|
|
REQUIRE(timeline->requestItemResize(cid6, size, true, true, undo, redo, false));
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
auto state2 = [&]() {
|
|
REQUIRE(timeline->getClipsCount() == clipCount + 1);
|
|
REQUIRE(timeline->isClip(cid6));
|
|
REQUIRE(timeline->getClipTrackId(cid6) == -1);
|
|
REQUIRE(timeline->getClipPlaytime(cid6) == l - 5);
|
|
};
|
|
state2();
|
|
|
|
{
|
|
std::function<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
REQUIRE(timeline->requestClipMove(cid6, tid1, 7, true, true, true, true, undo, redo) == TimelineModel::MoveSuccess);
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
auto state3 = [&]() {
|
|
REQUIRE(timeline->getClipsCount() == 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<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
int size = l - 6;
|
|
REQUIRE(timeline->requestItemResize(cid6, size, false, true, undo, redo, false));
|
|
pCore->pushUndo(undo, redo, QString());
|
|
}
|
|
auto state4 = [&]() {
|
|
REQUIRE(timeline->getClipsCount() == 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();
|
|
}
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Snapping", "[Snapping]")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
Mock<KdenliveDoc> docMock(document);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
RESET(timMock);
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel, 50);
|
|
QString binId2 = KdenliveTests::createProducer(pCore->getProjectProfile(), "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 length = timeline->getClipPlaytime(cid1);
|
|
int length2 = timeline->getClipPlaytime(cid2);
|
|
SECTION("getBlankSizeNearClip")
|
|
{
|
|
REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
|
|
|
|
// before
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, false) == 0);
|
|
// after
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
|
|
REQUIRE(timeline->requestClipMove(cid1, tid1, 10));
|
|
// before
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, false) == 10);
|
|
// after
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
|
|
REQUIRE(timeline->requestClipMove(cid2, tid1, 25 + length));
|
|
// before
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, false) == 10);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid2, false) == 15);
|
|
// after
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, true) == 15);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX);
|
|
|
|
REQUIRE(timeline->requestClipMove(cid2, tid1, 10 + length));
|
|
// before
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, false) == 10);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid2, false) == 0);
|
|
// after
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->getBlankSizeNearClip(cid1, true) == 0);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, 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).at(0) == beg);
|
|
REQUIRE(timeline->suggestClipMove(cid2, tid2, beg + length, -1, snap).at(0) == 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).at(0) == pos);
|
|
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap).at(0) == pos);
|
|
} else {
|
|
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap).at(0) == pos + perturb);
|
|
REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap).at(0) == 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());
|
|
}
|
|
}
|
|
}
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("Operations under locked tracks", "[Locked]")
|
|
{
|
|
|
|
QString aCompo;
|
|
// Look for a compo
|
|
QVector<QPair<QString, QString>> transitions = TransitionsRepository::get()->getNames();
|
|
for (const auto &trans : std::as_const(transitions)) {
|
|
if (TransitionsRepository::get()->isComposition(trans.first)) {
|
|
aCompo = trans.first;
|
|
break;
|
|
}
|
|
}
|
|
REQUIRE(!aCompo.isEmpty());
|
|
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
|
|
// Here we do some trickery to enable testing.
|
|
// We mock the project class so that the undoStack function returns our undoStack
|
|
KdenliveDoc document(undoStack);
|
|
Mock<KdenliveDoc> docMock(document);
|
|
|
|
// We also mock timeline object to spy few functions and mock others
|
|
pCore->projectManager()->testSetDocument(&document);
|
|
std::shared_ptr<TimelineItemModel> tim = KdenliveTests::createTimelineModel(document.uuid(), undoStack);
|
|
Mock<TimelineItemModel> timMock(*tim.get());
|
|
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
|
|
KdenliveTests::finishTimelineConstruct(timeline);
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
Fake(Method(timMock, adjustAssetRange));
|
|
|
|
// This is faked to allow to count calls
|
|
Fake(Method(timMock, _beginInsertRows));
|
|
Fake(Method(timMock, _beginRemoveRows));
|
|
Fake(Method(timMock, _endInsertRows));
|
|
Fake(Method(timMock, _endRemoveRows));
|
|
|
|
QString binId = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel);
|
|
QString binId3 = KdenliveTests::createProducerWithSound(pCore->getProjectProfile(), binModel);
|
|
|
|
int tid1, tid2, tid3;
|
|
REQUIRE(timeline->requestTrackInsertion(-1, tid1));
|
|
REQUIRE(timeline->requestTrackInsertion(-1, tid2));
|
|
REQUIRE(timeline->requestTrackInsertion(-1, tid3));
|
|
|
|
RESET(timMock);
|
|
|
|
SECTION("Locked track can't receive insertion")
|
|
{
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->getClipsCount() == 0);
|
|
REQUIRE(timeline->checkConsistency());
|
|
int cid1 = -1;
|
|
REQUIRE_FALSE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
|
|
REQUIRE(timeline->getClipsCount() == 0);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(cid1 == -1);
|
|
|
|
// now unlock and check that insertion becomes possible again
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->getClipsCount() == 0);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 2);
|
|
}
|
|
SECTION("Can't move clip on locked track")
|
|
{
|
|
int cid1 = -1;
|
|
REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 2);
|
|
// not yet locked, move should work
|
|
REQUIRE(timeline->requestClipMove(cid1, tid1, 4));
|
|
REQUIRE(timeline->getClipPosition(cid1) == 4);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 4);
|
|
|
|
REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 6));
|
|
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 4);
|
|
|
|
// unlock, move should work again
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->requestClipMove(cid1, tid1, 6));
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 6);
|
|
REQUIRE(timeline->checkConsistency());
|
|
}
|
|
SECTION("Can't move composition on locked track")
|
|
{
|
|
int compo = CompositionModel::construct(timeline, aCompo, QString());
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
|
|
REQUIRE(timeline->getCompositionTrackId(compo) == -1);
|
|
REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0);
|
|
int pos = 10;
|
|
REQUIRE_FALSE(timeline->requestCompositionMove(compo, tid1, pos));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getCompositionTrackId(compo) == -1);
|
|
REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0);
|
|
|
|
// unlock to be able to insert
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->requestCompositionMove(compo, tid1, pos));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
|
|
REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1);
|
|
REQUIRE(timeline->getCompositionPosition(compo) == pos);
|
|
|
|
// relock
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE_FALSE(timeline->requestCompositionMove(compo, tid1, pos + 10));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
|
|
REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1);
|
|
REQUIRE(timeline->getCompositionPosition(compo) == pos);
|
|
}
|
|
SECTION("Can't resize clip on locked track")
|
|
{
|
|
int cid1 = -1;
|
|
REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
|
|
auto check = [&](int l) {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 2);
|
|
REQUIRE(timeline->getClipPlaytime(cid1) == l);
|
|
};
|
|
check(20);
|
|
|
|
// not yet locked, resize should work
|
|
REQUIRE(timeline->requestItemResize(cid1, 18, true) == 18);
|
|
check(18);
|
|
|
|
// lock
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(cid1, 17, true) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(cid1, 17, false) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(cid1, 19, true) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(cid1, 19, false) == -1);
|
|
check(18);
|
|
|
|
// unlock, resize should work again
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(cid1, 17, true) == 17);
|
|
check(17);
|
|
}
|
|
SECTION("Can't resize composition on locked track")
|
|
{
|
|
int compo = CompositionModel::construct(timeline, aCompo, QString());
|
|
REQUIRE(timeline->requestCompositionMove(compo, tid1, 2));
|
|
REQUIRE(timeline->requestItemResize(compo, 20, true) == 20);
|
|
|
|
auto check = [&](int l) {
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getCompositionsCount() == 1);
|
|
REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
|
|
REQUIRE(timeline->getCompositionPosition(compo) == 2);
|
|
REQUIRE(timeline->getCompositionPlaytime(compo) == l);
|
|
};
|
|
check(20);
|
|
|
|
// not yet locked, resize should work
|
|
REQUIRE(timeline->requestItemResize(compo, 18, true) == 18);
|
|
check(18);
|
|
|
|
// lock
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(compo, 17, true) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(compo, 17, false) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(compo, 19, true) == -1);
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(compo, 19, false) == -1);
|
|
check(18);
|
|
|
|
// unlock, resize should work again
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
check(18);
|
|
REQUIRE(timeline->requestItemResize(compo, 17, true) == 17);
|
|
check(17);
|
|
}
|
|
SECTION("Can't remove clip contained in locked track")
|
|
{
|
|
std::function<bool(void)> undo = []() { return true; };
|
|
std::function<bool(void)> redo = []() { return true; };
|
|
|
|
// insert a clip to the track
|
|
int cid1 = -1;
|
|
REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 2);
|
|
|
|
// lock the track
|
|
timeline->setTrackLockedState(tid1, true);
|
|
REQUIRE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
|
|
// try to delete bin clip, this should not work
|
|
REQUIRE_FALSE(binModel->requestBinClipDeletion(binModel->getClipByBinID(binId), undo, redo));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 1);
|
|
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
|
|
REQUIRE(timeline->getClipPosition(cid1) == 2);
|
|
|
|
// unlock track, bin clip deletion should work now
|
|
timeline->setTrackLockedState(tid1, false);
|
|
REQUIRE_FALSE(KdenliveTests::getTrackById_const(timeline, tid1)->isLocked());
|
|
REQUIRE(binModel->requestBinClipDeletion(binModel->getClipByBinID(binId), undo, redo));
|
|
REQUIRE(timeline->checkConsistency());
|
|
REQUIRE(timeline->getClipsCount() == 0);
|
|
REQUIRE(timeline->checkConsistency());
|
|
}
|
|
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
TEST_CASE("New KdenliveDoc activeTrack", "KdenliveDoc")
|
|
{
|
|
auto binModel = pCore->projectItemModel();
|
|
binModel->clean();
|
|
|
|
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
|
|
QUndoGroup *undoGroup = new QUndoGroup();
|
|
undoGroup->addStack(undoStack.get());
|
|
const QMap<QString, QString> emptyMap{};
|
|
|
|
// Bug 442545: KdenliveDoc created with 0 video tracks causes a crash at
|
|
// save time because the document's activeTrack was set to an out-of-range
|
|
// position.
|
|
|
|
SECTION("0 video tracks")
|
|
{
|
|
// Create document
|
|
KdenliveDoc doc(undoStack, {0, 2});
|
|
pCore->projectManager()->testSetDocument(&doc);
|
|
QDateTime documentDate = QDateTime::currentDateTime();
|
|
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
|
|
QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
|
|
const QString firstSeqId = allSequences.value(doc.uuid());
|
|
pCore->projectManager()->openTimeline(firstSeqId, -1, doc.uuid());
|
|
auto timeline = doc.getTimeline(doc.uuid());
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
// since there are only 2 tracks, the activeTrack position should be 0 or 1
|
|
CHECK(doc.getDocumentProperty("activeTrack").toInt() >= 0);
|
|
CHECK(doc.getDocumentProperty("activeTrack").toInt() < 2);
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
SECTION("both audio and video tracks")
|
|
{
|
|
KdenliveDoc doc(undoStack, {2, 2});
|
|
pCore->projectManager()->testSetDocument(&doc);
|
|
QDateTime documentDate = QDateTime::currentDateTime();
|
|
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
|
|
QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
|
|
const QString firstSeqId = allSequences.value(doc.uuid());
|
|
pCore->projectManager()->openTimeline(firstSeqId, -1, doc.uuid());
|
|
auto timeline = doc.getTimeline(doc.uuid());
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() >= 0);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() < 4);
|
|
// because video tracks come after audio tracks, videoTarget position
|
|
// should also be after the audio tracks
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() > 1);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() < 4);
|
|
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "audioTarget").toInt() >= 0);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "audioTarget").toInt() < 2);
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
|
|
SECTION("0 audio tracks")
|
|
{
|
|
KdenliveDoc doc(undoStack, {2, 0});
|
|
pCore->projectManager()->testSetDocument(&doc);
|
|
QDateTime documentDate = QDateTime::currentDateTime();
|
|
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
|
|
QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
|
|
const QString firstSeqId = allSequences.value(doc.uuid());
|
|
pCore->projectManager()->openTimeline(firstSeqId, -1, doc.uuid());
|
|
auto timeline = doc.getTimeline(doc.uuid());
|
|
pCore->projectManager()->testSetActiveTimeline(timeline);
|
|
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() >= 0);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() < 2);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() >= 0);
|
|
CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() < 2);
|
|
pCore->projectManager()->closeCurrentDocument(false, false);
|
|
}
|
|
}
|