Commit 4fb549a7 authored by Nicolas Carion's avatar Nicolas Carion

make lock track undoable and other fixes for locking + tests

parent 046df4ca
......@@ -176,6 +176,9 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool l
}
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
if (ptr->getTrackById(m_currentTrackId)->isLocked()) {
return false;
}
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";
......
......@@ -104,6 +104,9 @@ bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo,
std::function<bool(void)> track_reverse = []() { return true; };
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
if (ptr->getTrackById(m_currentTrackId)->isLocked()) {
return false;
}
track_operation = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, in, out, logUndo);
} else {
qDebug() << "Error : Moving composition failed because parent timeline is not available anymore";
......
......@@ -1350,7 +1350,7 @@ bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, i
// Sort clips first
std::vector<int> sorted_clips(all_clips.begin(), all_clips.end());
std::sort(sorted_clips.begin(), sorted_clips.end(), [this, delta_track, delta_pos](int clipId1, int clipId2) {
std::sort(sorted_clips.begin(), sorted_clips.end(), [this, delta_pos](int clipId1, int clipId2) {
int p1 = m_allClips[clipId1]->getPosition();
int p2 = m_allClips[clipId2]->getPosition();
return delta_pos > 0 ? p2 <= p1 : p1 <= p2;
......@@ -1703,6 +1703,7 @@ int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logU
bool result = true;
int finalPos = right ? in + size : out - size;
int finalSize;
int resizedCount = 0;
for (int id : all_items) {
int tid = getItemTrackId(id);
if (tid > -1 && getTrackById_const(tid)->isLocked()) {
......@@ -1714,8 +1715,9 @@ int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logU
finalSize = getItemPosition(id) + getItemPlaytime(id) - finalPos;
}
result = result && requestItemResize(id, finalSize, right, logUndo, undo, redo);
resizedCount++;
}
if (!result) {
if (!result || resizedCount == 0) {
bool undone = undo();
Q_ASSERT(undone);
TRACE_RES(-1);
......@@ -3239,6 +3241,7 @@ bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids)
bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Fun reverse = [this]() {
requestClearSelection(false);
return true;
......@@ -3253,16 +3256,35 @@ bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids, Fun
void TimelineModel::setTrackLockedState(int trackId, bool lock)
{
QWriteLocker locker(&m_lock);
TRACE(trackId, lock);
if (lock) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Fun lock_lambda = [this, trackId]() {
getTrackById(trackId)->lock();
} else {
return true;
};
Fun unlock_lambda = [this, trackId]() {
getTrackById(trackId)->unlock();
return true;
};
if (lock) {
if (lock_lambda()) {
UPDATE_UNDO_REDO(lock_lambda, unlock_lambda, undo, redo);
PUSH_UNDO(undo, redo, i18n("Lock track"));
}
} else {
if (unlock_lambda()) {
UPDATE_UNDO_REDO(unlock_lambda, lock_lambda, undo, redo);
PUSH_UNDO(undo, redo, i18n("Unlock track"));
}
}
}
std::unordered_set<int> TimelineModel::getAllTracksIds() const
{
READ_LOCK();
std::unordered_set<int> result;
std::transform(m_iteratorTable.begin(), m_iteratorTable.end(), std::inserter(result, result.begin()), [&](const auto &track) { return track.first; });
return result;
......
......@@ -166,6 +166,7 @@ Fun TrackModel::requestClipInsertion_lambda(int clipId, int position, bool updat
if (target_clip >= count && isBlankAt(position)) {
// In that case, we append after, in the first playlist
return [this, position, clipId, end_function, finalMove]() {
if (isLocked()) return false;
if (auto ptr = m_parent.lock()) {
// Lock MLT playlist so that we don't end up with an invalid frame being displayed
m_playlists[0].lock();
......@@ -192,6 +193,7 @@ Fun TrackModel::requestClipInsertion_lambda(int clipId, int position, bool updat
}
if (blank_end >= position + length) {
return [this, position, clipId, end_function]() {
if (isLocked()) return false;
if (auto ptr = m_parent.lock()) {
// Lock MLT playlist so that we don't end up with an invalid frame being displayed
m_playlists[0].lock();
......@@ -285,6 +287,7 @@ Fun TrackModel::requestClipDeletion_lambda(int clipId, bool updateView, bool fin
int old_in = clip_position;
int old_out = old_in + m_allClips[clipId]->getPlaytime();
return [clip_position, clipId, old_in, old_out, updateView, audioOnly, finalMove, this]() {
if (isLocked()) return false;
auto clip_loc = getClipIndexAt(clip_position);
if (updateView) {
int old_clip_index = getRowfromClip(clipId);
......@@ -476,7 +479,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
checkRefresh = true;
}
auto update_snaps = [old_in, old_out, clipId, checkRefresh, this](int new_in, int new_out) {
auto update_snaps = [old_in, old_out, checkRefresh, this](int new_in, int new_out) {
if (auto ptr = m_parent.lock()) {
ptr->m_snaps->removePoint(old_in);
ptr->m_snaps->removePoint(old_out);
......@@ -500,6 +503,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
// qDebug() << "RESIZING CLIP: " << clipId << " FROM: " << delta;
if (delta > 0) { // we shrink clip
return [right, target_clip, target_track, clip_position, delta, in, out, clipId, update_snaps, this]() {
if (isLocked()) return false;
int target_clip_mutable = target_clip;
int blank_index = right ? (target_clip_mutable + 1) : target_clip_mutable;
// insert blank to space that is going to be empty
......@@ -531,6 +535,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
if (target_clip == m_playlists[target_track].count() - 1 && other_blank_end >= out) {
// clip is last, it can always be extended
return [this, target_clip, target_track, in, out, update_snaps, clipId]() {
if (isLocked()) return false;
// color, image and title clips can have unlimited resize
QScopedPointer<Mlt::Producer> clip(m_playlists[target_track].get_clip(target_clip));
if (out >= clip->get_length()) {
......@@ -565,6 +570,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
int blank_length = m_playlists[target_track].clip_length(blank);
if (blank_length + delta >= 0 && other_blank_end >= out) {
return [blank_length, blank, right, clipId, delta, update_snaps, this, in, out, target_clip, target_track]() {
if (isLocked()) return false;
int target_clip_mutable = target_clip;
int err = 0;
if (blank_length + delta == 0) {
......@@ -1008,6 +1014,7 @@ Fun TrackModel::requestCompositionResize_lambda(int compoId, int in, int out, bo
}
return [in, out, compoId, update_snaps, this]() {
if (isLocked()) return false;
m_compoPos.erase(m_allCompositions[compoId]->getPosition());
m_allCompositions[compoId]->setInOut(in, out);
update_snaps(in, out + 1);
......@@ -1059,6 +1066,7 @@ Fun TrackModel::requestCompositionDeletion_lambda(int compoId, bool updateView,
int old_in = clip_position;
int old_out = old_in + m_allCompositions[compoId]->getPlaytime();
return [compoId, old_in, old_out, updateView, finalMove, this]() {
if (isLocked()) return false;
int old_clip_index = getRowfromComposition(compoId);
auto ptr = m_parent.lock();
if (updateView) {
......@@ -1106,6 +1114,7 @@ Fun TrackModel::requestCompositionInsertion_lambda(int compoId, int position, bo
}
if (!intersecting) {
return [compoId, this, position, updateView, finalMove]() {
if (isLocked()) return false;
if (auto ptr = m_parent.lock()) {
std::shared_ptr<CompositionModel> composition = ptr->getCompositionPtr(compoId);
m_allCompositions[composition->getId()] = composition; // store clip
......
......@@ -82,14 +82,10 @@ public:
*/
operator Mlt::Producer &() { return *m_track.get(); }
/* @brief This will lock the track: it will no longer allow insertion/deletion/resize of items */
void lock();
void unlock();
/* @brief Returns true if track is in locked state
*/
bool isLocked() const;
/* @brief Returns true if track is active in timeline, ie.
/* @brief Returns true if track is active in timeline, ie.
* will receive insert/lift/overwrite/extract operations
*/
bool isTimelineActive() const;
......@@ -114,6 +110,13 @@ public:
void setProperty(const QString &name, const QString &value);
protected:
/* @brief This will lock the track: it will no longer allow insertion/deletion/resize of items
This functions are dangerous to call directly since locking the track will potentially
mess up with the undo/redo system (a track lock may make an undo impossible).
Prefer calling TimelineModel::setTrackLockedState */
void lock();
void unlock();
/* @brief Returns a lambda that performs a resize of the given clip.
The lamda returns true if the operation succeeded, and otherwise nothing is modified
This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
......
......@@ -1140,7 +1140,6 @@ TEST_CASE("Undo and Redo", "[ClipModel]")
int tid2 = TrackModel::construct(timeline);
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
int length = 20;
int nclips = timeline->m_allClips.size();
......@@ -1756,8 +1755,6 @@ TEST_CASE("Snapping", "[Snapping]")
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);
int length = timeline->getClipPlaytime(cid1);
int length2 = timeline->getClipPlaytime(cid2);
......@@ -1843,3 +1840,237 @@ TEST_CASE("Snapping", "[Snapping]")
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
TEST_CASE("Operations under locked tracks", "[Locked]")
{
Logger::clear();
QString aCompo;
// Look for a compo
QVector<QPair<QString, QString>> transitions = TransitionsRepository::get()->getNames();
for (const auto &trans : 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);
std::shared_ptr<MarkerListModel> guideModel = std::make_shared<MarkerListModel>(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock<ProjectManager> 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<TimelineItemModel> timMock(tim);
auto timeline = std::shared_ptr<TimelineItemModel>(&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 binId3 = createProducerWithSound(profile_model, binModel);
int tid1, tid2, tid3;
REQUIRE(timeline->requestTrackInsertion(-1, tid1));
REQUIRE(timeline->requestTrackInsertion(-1, tid2));
REQUIRE(timeline->requestTrackInsertion(-1, tid3));
Verify(Method(timMock, _resetView)).Exactly(3_Times);
RESET(timMock);
SECTION("Locked track can't receive insertion")
{
timeline->setTrackLockedState(tid1, true);
REQUIRE(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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);
timeline->setTrackLockedState(tid1, true);
REQUIRE(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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(timeline->getTrackById(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);
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(timeline->getTrackById(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(timeline->getTrackById(tid1)->isLocked());
check(18);
REQUIRE(timeline->requestItemResize(compo, 17, true) == 17);
check(17);
}
binModel->clean();
pCore->m_projectManager = nullptr;
Logger::print_trace();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment