diff --git a/src/timeline2/model/trackmodel.cpp b/src/timeline2/model/trackmodel.cpp index 167b91b17..f029ac051 100644 --- a/src/timeline2/model/trackmodel.cpp +++ b/src/timeline2/model/trackmodel.cpp @@ -1,1147 +1,1150 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "trackmodel.hpp" #include "clipmodel.hpp" #include "compositionmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "kdenlivesettings.h" #include "logger.hpp" #include "snapmodel.hpp" #include "timelinemodel.hpp" #include #include #include TrackModel::TrackModel(const std::weak_ptr &parent, int id, const QString &trackName, bool audioTrack) : m_parent(parent) , m_id(id == -1 ? TimelineModel::getNextId() : id) , m_lock(QReadWriteLock::Recursive) { if (auto ptr = parent.lock()) { m_track = std::make_shared(*ptr->getProfile()); m_playlists[0].set_profile(*ptr->getProfile()); m_playlists[1].set_profile(*ptr->getProfile()); m_track->insert_track(m_playlists[0], 0); m_track->insert_track(m_playlists[1], 1); if (!trackName.isEmpty()) { m_track->set("kdenlive:track_name", trackName.toUtf8().constData()); } if (audioTrack) { m_track->set("kdenlive:audio_track", 1); for (auto &m_playlist : m_playlists) { m_playlist.set("hide", 1); } } m_track->set("kdenlive:trackheight", KdenliveSettings::trackheight()); m_effectStack = EffectStackModel::construct(m_track, {ObjectType::TimelineTrack, m_id}, ptr->m_undoStack); QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, QVector roles) { if (auto ptr2 = m_parent.lock()) { QModelIndex ix = ptr2->makeTrackIndexFromID(m_id); ptr2->dataChanged(ix, ix, roles); } }); } else { qDebug() << "Error : construction of track failed because parent timeline is not available anymore"; Q_ASSERT(false); } } TrackModel::TrackModel(const std::weak_ptr &parent, Mlt::Tractor mltTrack, int id) : m_parent(parent) , m_id(id == -1 ? TimelineModel::getNextId() : id) { if (auto ptr = parent.lock()) { m_track = std::make_shared(mltTrack); m_playlists[0] = *m_track->track(0); m_playlists[1] = *m_track->track(1); m_effectStack = EffectStackModel::construct(m_track, {ObjectType::TimelineTrack, m_id}, ptr->m_undoStack); } else { qDebug() << "Error : construction of track failed because parent timeline is not available anymore"; Q_ASSERT(false); } } TrackModel::~TrackModel() { m_track->remove_track(1); m_track->remove_track(0); } int TrackModel::construct(const std::weak_ptr &parent, int id, int pos, const QString &trackName, bool audioTrack) { std::shared_ptr track(new TrackModel(parent, id, trackName, audioTrack)); TRACE_CONSTR(track.get(), parent, id, pos, trackName, audioTrack); id = track->m_id; if (auto ptr = parent.lock()) { ptr->registerTrack(std::move(track), pos); } else { qDebug() << "Error : construction of track failed because parent timeline is not available anymore"; Q_ASSERT(false); } return id; } int TrackModel::getClipsCount() { READ_LOCK(); #ifdef QT_DEBUG int count = 0; for (auto &m_playlist : m_playlists) { for (int i = 0; i < m_playlist.count(); i++) { if (!m_playlist.is_blank(i)) { count++; } } } Q_ASSERT(count == static_cast(m_allClips.size())); #else int count = (int)m_allClips.size(); #endif return count; } Fun TrackModel::requestClipInsertion_lambda(int clipId, int position, bool updateView, bool finalMove) { QWriteLocker locker(&m_lock); // By default, insertion occurs in topmost track // Find out the clip id at position int target_clip = m_playlists[0].get_clip_index_at(position); int count = m_playlists[0].count(); if (auto ptr = m_parent.lock()) { Q_ASSERT(ptr->getClipPtr(clipId)->getCurrentTrackId() == -1); } else { qDebug() << "impossible to get parent timeline"; Q_ASSERT(false); } // we create the function that has to be executed after the melt order. This is essentially book-keeping auto end_function = [clipId, this, position, updateView, finalMove]() { if (auto ptr = m_parent.lock()) { std::shared_ptr clip = ptr->getClipPtr(clipId); m_allClips[clip->getId()] = clip; // store clip // update clip position and track clip->setPosition(position); int new_in = clip->getPosition(); int new_out = new_in + clip->getPlaytime(); ptr->m_snaps->addPoint(new_in); ptr->m_snaps->addPoint(new_out); if (updateView) { int clip_index = getRowfromClip(clipId); ptr->_beginInsertRows(ptr->makeTrackIndexFromID(m_id), clip_index, clip_index); ptr->_endInsertRows(); bool audioOnly = clip->isAudioOnly(); if (!audioOnly && !isHidden() && !isAudioTrack()) { // only refresh monitor if not an audio track and not hidden ptr->checkRefresh(new_in, new_out); } if (!audioOnly && finalMove && !isAudioTrack()) { ptr->invalidateZone(new_in, new_out); } } return true; } qDebug() << "Error : Clip Insertion failed because timeline is not available anymore"; return false; }; if (target_clip >= count && isBlankAt(position)) { // In that case, we append after, in the first playlist return [this, position, clipId, end_function, finalMove]() { 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(); std::shared_ptr clip = ptr->getClipPtr(clipId); clip->setCurrentTrackId(m_id, finalMove); int index = m_playlists[0].insert_at(position, *clip, 1); m_playlists[0].consolidate_blanks(); m_playlists[0].unlock(); if (finalMove) { ptr->updateDuration(); } return index != -1 && end_function(); } qDebug() << "Error : Clip Insertion failed because timeline is not available anymore"; return false; }; } if (isBlankAt(position)) { int blank_end = getBlankEnd(position); int length = -1; if (auto ptr = m_parent.lock()) { std::shared_ptr clip = ptr->getClipPtr(clipId); length = clip->getPlaytime(); } if (blank_end >= position + length) { return [this, position, clipId, end_function]() { 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(); std::shared_ptr clip = ptr->getClipPtr(clipId); clip->setCurrentTrackId(m_id); int index = m_playlists[0].insert_at(position, *clip, 1); m_playlists[0].consolidate_blanks(); m_playlists[0].unlock(); return index != -1 && end_function(); } qDebug() << "Error : Clip Insertion failed because timeline is not available anymore"; return false; }; } } return []() { return false; }; } bool TrackModel::requestClipInsertion(int clipId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (isLocked()) { return false; } + if (position < 0) { + return false; + } if (auto ptr = m_parent.lock()) { if (isAudioTrack() && !ptr->getClipPtr(clipId)->canBeAudio()) { qDebug() << "// ATTEMPTING TO INSERT NON AUDIO CLIP ON AUDIO TRACK"; return false; } if (!isAudioTrack() && !ptr->getClipPtr(clipId)->canBeVideo()) { qDebug() << "// ATTEMPTING TO INSERT NON VIDEO CLIP ON VIDEO TRACK"; return false; } Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; bool res = true; if (ptr->getClipPtr(clipId)->clipState() != PlaylistState::Disabled) { res = res && ptr->getClipPtr(clipId)->setClipState(isAudioTrack() ? PlaylistState::AudioOnly : PlaylistState::VideoOnly, local_undo, local_redo); } auto operation = requestClipInsertion_lambda(clipId, position, updateView, finalMove); res = res && operation(); if (res) { auto reverse = requestClipDeletion_lambda(clipId, updateView, finalMove); UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo); UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } bool undone = local_undo(); Q_ASSERT(undone); return false; } return false; } void TrackModel::replugClip(int clipId) { QWriteLocker locker(&m_lock); int clip_position = m_allClips[clipId]->getPosition(); auto clip_loc = getClipIndexAt(clip_position); int target_track = clip_loc.first; int target_clip = clip_loc.second; // lock MLT playlist so that we don't end up with invalid frames in monitor m_playlists[target_track].lock(); Q_ASSERT(target_clip < m_playlists[target_track].count()); Q_ASSERT(!m_playlists[target_track].is_blank(target_clip)); std::unique_ptr prod(m_playlists[target_track].replace_with_blank(target_clip)); if (auto ptr = m_parent.lock()) { std::shared_ptr clip = ptr->getClipPtr(clipId); m_playlists[target_track].insert_at(clip_position, *clip, 1); if (!clip->isAudioOnly() && !isAudioTrack()) { ptr->invalidateZone(clip->getIn(), clip->getOut()); } if (!clip->isAudioOnly() && !isHidden() && !isAudioTrack()) { // only refresh monitor if not an audio track and not hidden ptr->checkRefresh(clip->getIn(), clip->getOut()); } } m_playlists[target_track].consolidate_blanks(); m_playlists[target_track].unlock(); } Fun TrackModel::requestClipDeletion_lambda(int clipId, bool updateView, bool finalMove) { QWriteLocker locker(&m_lock); // Find index of clip int clip_position = m_allClips[clipId]->getPosition(); bool audioOnly = m_allClips[clipId]->isAudioOnly(); 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]() { auto clip_loc = getClipIndexAt(clip_position); if (updateView) { int old_clip_index = getRowfromClip(clipId); auto ptr = m_parent.lock(); ptr->_beginRemoveRows(ptr->makeTrackIndexFromID(getId()), old_clip_index, old_clip_index); ptr->_endRemoveRows(); } int target_track = clip_loc.first; int target_clip = clip_loc.second; // lock MLT playlist so that we don't end up with invalid frames in monitor m_playlists[target_track].lock(); Q_ASSERT(target_clip < m_playlists[target_track].count()); Q_ASSERT(!m_playlists[target_track].is_blank(target_clip)); auto prod = m_playlists[target_track].replace_with_blank(target_clip); if (prod != nullptr) { m_playlists[target_track].consolidate_blanks(); m_allClips[clipId]->setCurrentTrackId(-1); m_allClips.erase(clipId); delete prod; m_playlists[target_track].unlock(); if (auto ptr = m_parent.lock()) { ptr->m_snaps->removePoint(old_in); ptr->m_snaps->removePoint(old_out); if (finalMove) { if (!audioOnly && !isAudioTrack()) { ptr->invalidateZone(old_in, old_out); } if (target_clip >= m_playlists[target_track].count()) { // deleted last clip in playlist ptr->updateDuration(); } } if (!audioOnly && !isHidden() && !isAudioTrack()) { // only refresh monitor if not an audio track and not hidden ptr->checkRefresh(old_in, old_out); } } return true; } m_playlists[target_track].unlock(); return false; }; } bool TrackModel::requestClipDeletion(int clipId, bool updateView, bool finalMove, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_allClips.count(clipId) > 0); if (isLocked()) { return false; } auto old_clip = m_allClips[clipId]; int old_position = old_clip->getPosition(); // qDebug() << "/// REQUESTOING CLIP DELETION_: " << updateView; auto operation = requestClipDeletion_lambda(clipId, updateView, finalMove); if (operation()) { auto reverse = requestClipInsertion_lambda(clipId, old_position, updateView, finalMove); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } int TrackModel::getBlankSizeAtPos(int frame) { READ_LOCK(); int min_length = 0; for (auto &m_playlist : m_playlists) { int ix = m_playlist.get_clip_index_at(frame); if (m_playlist.is_blank(ix)) { int blank_length = m_playlist.clip_length(ix); if (min_length == 0 || (blank_length > 0 && blank_length < min_length)) { min_length = blank_length; } } } return min_length; } int TrackModel::suggestCompositionLength(int position) { READ_LOCK(); if (m_playlists[0].is_blank_at(position) && m_playlists[1].is_blank_at(position)) { return -1; } auto clip_loc = getClipIndexAt(position); int track = clip_loc.first; int index = clip_loc.second; int other_index; // index in the other track int other_track = (track + 1) % 2; int end_pos = m_playlists[track].clip_start(index) + m_playlists[track].clip_length(index); other_index = m_playlists[other_track].get_clip_index_at(end_pos); if (other_index < m_playlists[other_track].count()) { end_pos = std::min(end_pos, m_playlists[other_track].clip_start(other_index) + m_playlists[other_track].clip_length(other_index)); } int min = -1; std::unordered_set existing = getCompositionsInRange(position, end_pos); if (existing.size() > 0) { for (int id : existing) { if (min < 0) { min = m_allCompositions[id]->getPosition(); } else { min = qMin(min, m_allCompositions[id]->getPosition()); } } } if (min >= 0) { // An existing composition is limiting the space end_pos = min; } return end_pos - position; } int TrackModel::getBlankSizeNearClip(int clipId, bool after) { READ_LOCK(); Q_ASSERT(m_allClips.count(clipId) > 0); int clip_position = m_allClips[clipId]->getPosition(); auto clip_loc = getClipIndexAt(clip_position); int track = clip_loc.first; int index = clip_loc.second; int other_index; // index in the other track int other_track = (track + 1) % 2; if (after) { int first_pos = m_playlists[track].clip_start(index) + m_playlists[track].clip_length(index); other_index = m_playlists[other_track].get_clip_index_at(first_pos); index++; } else { int last_pos = m_playlists[track].clip_start(index) - 1; other_index = m_playlists[other_track].get_clip_index_at(last_pos); index--; } if (index < 0) return 0; int length = INT_MAX; if (index < m_playlists[track].count()) { if (!m_playlists[track].is_blank(index)) { return 0; } length = std::min(length, m_playlists[track].clip_length(index)); } if (other_index < m_playlists[other_track].count()) { if (!m_playlists[other_track].is_blank(other_index)) { return 0; } length = std::min(length, m_playlists[other_track].clip_length(other_index)); } return length; } int TrackModel::getBlankSizeNearComposition(int compoId, bool after) { READ_LOCK(); Q_ASSERT(m_allCompositions.count(compoId) > 0); int clip_position = m_allCompositions[compoId]->getPosition(); Q_ASSERT(m_compoPos.count(clip_position) > 0); Q_ASSERT(m_compoPos[clip_position] == compoId); auto it = m_compoPos.find(clip_position); int clip_length = m_allCompositions[compoId]->getPlaytime(); int length = INT_MAX; if (after) { ++it; if (it != m_compoPos.end()) { return it->first - clip_position - clip_length; } } else { if (it != m_compoPos.begin()) { --it; return clip_position - it->first - m_allCompositions[it->second]->getPlaytime(); } return clip_position; } return length; } Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right) { QWriteLocker locker(&m_lock); int clip_position = m_allClips[clipId]->getPosition(); int old_in = clip_position; int old_out = old_in + m_allClips[clipId]->getPlaytime(); auto clip_loc = getClipIndexAt(clip_position); int target_track = clip_loc.first; int target_clip = clip_loc.second; Q_ASSERT(target_clip < m_playlists[target_track].count()); int size = out - in + 1; bool checkRefresh = false; if (!isHidden() && !isAudioTrack()) { checkRefresh = true; } 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); ptr->m_snaps->addPoint(new_in); ptr->m_snaps->addPoint(new_out); if (checkRefresh) { ptr->checkRefresh(old_in, old_out); ptr->checkRefresh(new_in, new_out); // ptr->adjustAssetRange(clipId, m_allClips[clipId]->getIn(), m_allClips[clipId]->getOut()); } } else { qDebug() << "Error : clip resize failed because parent timeline is not available anymore"; Q_ASSERT(false); } }; int delta = m_allClips[clipId]->getPlaytime() - size; if (delta == 0) { return []() { return true; }; } // 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]() { 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 // The second is parameter is delta - 1 because this function expects an out time, which is basically size - 1 m_playlists[target_track].insert_blank(blank_index, delta - 1); if (!right) { m_allClips[clipId]->setPosition(clip_position + delta); // Because we inserted blank before, the index of our clip has increased target_clip_mutable++; } int err = m_playlists[target_track].resize_clip(target_clip_mutable, in, out); // make sure to do this after, to avoid messing the indexes m_playlists[target_track].consolidate_blanks(); if (err == 0) { update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1); if (right && m_playlists[target_track].count() - 1 == target_clip_mutable) { // deleted last clip in playlist if (auto ptr = m_parent.lock()) { ptr->updateDuration(); } } } return err == 0; }; } int blank = -1; int other_blank_end = getBlankEnd(clip_position, (target_track + 1) % 2); if (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]() { // color, image and title clips can have unlimited resize QScopedPointer clip(m_playlists[target_track].get_clip(target_clip)); if (out >= clip->get_length()) { clip->parent().set("length", out + 1); clip->parent().set("out", out); clip->set("length", out + 1); } int err = m_playlists[target_track].resize_clip(target_clip, in, out); if (err == 0) { update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1); } m_playlists[target_track].consolidate_blanks(); if (m_playlists[target_track].count() - 1 == target_clip) { // deleted last clip in playlist if (auto ptr = m_parent.lock()) { ptr->updateDuration(); } } return err == 0; }; } blank = target_clip + 1; } else { if (target_clip == 0) { // clip is first, it can never be extended on the left return []() { return false; }; } blank = target_clip - 1; } if (m_playlists[target_track].is_blank(blank)) { 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]() { int target_clip_mutable = target_clip; int err = 0; if (blank_length + delta == 0) { err = m_playlists[target_track].remove(blank); if (!right) { target_clip_mutable--; } } else { err = m_playlists[target_track].resize_clip(blank, 0, blank_length + delta - 1); } if (err == 0) { QScopedPointer clip(m_playlists[target_track].get_clip(target_clip_mutable)); if (out >= clip->get_length()) { clip->parent().set("length", out + 1); clip->parent().set("out", out); clip->set("length", out + 1); } err = m_playlists[target_track].resize_clip(target_clip_mutable, in, out); } if (!right && err == 0) { m_allClips[clipId]->setPosition(m_playlists[target_track].clip_start(target_clip_mutable)); } if (err == 0) { update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1); } m_playlists[target_track].consolidate_blanks(); return err == 0; }; } } return []() { return false; }; } int TrackModel::getId() const { return m_id; } int TrackModel::getClipByPosition(int position) { READ_LOCK(); QSharedPointer prod(nullptr); if (m_playlists[0].count() > 0) { prod = QSharedPointer(m_playlists[0].get_clip_at(position)); } if ((!prod || prod->is_blank()) && m_playlists[1].count() > 0) { prod = QSharedPointer(m_playlists[1].get_clip_at(position)); } if (!prod || prod->is_blank()) { return -1; } return prod->get_int("_kdenlive_cid"); } QSharedPointer TrackModel::getClipProducer(int clipId) { READ_LOCK(); QSharedPointer prod(nullptr); if (m_playlists[0].count() > 0) { prod = QSharedPointer(m_playlists[0].get_clip(clipId)); } if ((!prod || prod->is_blank()) && m_playlists[1].count() > 0) { prod = QSharedPointer(m_playlists[1].get_clip(clipId)); } return prod; } int TrackModel::getCompositionByPosition(int position) { READ_LOCK(); for (const auto &comp : m_compoPos) { if (comp.first == position) { return comp.second; } else if (comp.first < position) { if (comp.first + m_allCompositions[comp.second]->getPlaytime() >= position) { return comp.second; } } } return -1; } int TrackModel::getClipByRow(int row) const { READ_LOCK(); if (row >= static_cast(m_allClips.size())) { return -1; } auto it = m_allClips.cbegin(); std::advance(it, row); return (*it).first; } std::unordered_set TrackModel::getClipsInRange(int position, int end) { READ_LOCK(); std::unordered_set ids; for (const auto &clp : m_allClips) { int pos = clp.second->getPosition(); int length = clp.second->getPlaytime(); if (end > -1 && pos >= end) { continue; } if (pos >= position || pos + length - 1 >= position) { ids.insert(clp.first); } } return ids; } int TrackModel::getRowfromClip(int clipId) const { READ_LOCK(); Q_ASSERT(m_allClips.count(clipId) > 0); return (int)std::distance(m_allClips.begin(), m_allClips.find(clipId)); } std::unordered_set TrackModel::getCompositionsInRange(int position, int end) { READ_LOCK(); // TODO: this function doesn't take into accounts the fact that there are two tracks std::unordered_set ids; for (const auto &compo : m_allCompositions) { int pos = compo.second->getPosition(); int length = compo.second->getPlaytime(); if (end > -1 && pos >= end) { continue; } if (pos >= position || pos + length - 1 >= position) { ids.insert(compo.first); } } return ids; } int TrackModel::getRowfromComposition(int tid) const { READ_LOCK(); Q_ASSERT(m_allCompositions.count(tid) > 0); return (int)m_allClips.size() + (int)std::distance(m_allCompositions.begin(), m_allCompositions.find(tid)); } QVariant TrackModel::getProperty(const QString &name) const { READ_LOCK(); return QVariant(m_track->get(name.toUtf8().constData())); } void TrackModel::setProperty(const QString &name, const QString &value) { QWriteLocker locker(&m_lock); m_track->set(name.toUtf8().constData(), value.toUtf8().constData()); // Hide property mus be defined at playlist level or it won't be saved if (name == QLatin1String("kdenlive:audio_track") || name == QLatin1String("hide")) { for (auto &m_playlist : m_playlists) { m_playlist.set(name.toUtf8().constData(), value.toInt()); } } } bool TrackModel::checkConsistency() { auto ptr = m_parent.lock(); if (!ptr) { return false; } std::vector> clips; // clips stored by (position, id) for (const auto &c : m_allClips) { Q_ASSERT(c.second); Q_ASSERT(c.second.get() == ptr->getClipPtr(c.first).get()); clips.emplace_back(c.second->getPosition(), c.first); } std::sort(clips.begin(), clips.end()); size_t current_clip = 0; int playtime = std::max(m_playlists[0].get_playtime(), m_playlists[1].get_playtime()); for (int i = 0; i < playtime; i++) { int track, index; if (isBlankAt(i)) { track = 0; index = m_playlists[0].get_clip_index_at(i); } else { auto clip_loc = getClipIndexAt(i); track = clip_loc.first; index = clip_loc.second; } Q_ASSERT(m_playlists[(track + 1) % 2].is_blank_at(i)); if (current_clip < clips.size() && i >= clips[current_clip].first) { auto clip = m_allClips[clips[current_clip].second]; if (i >= clips[current_clip].first + clip->getPlaytime()) { current_clip++; i--; continue; } if (isBlankAt(i)) { qDebug() << "ERROR: Found blank when clip was required at position " << i; return false; } auto pr = m_playlists[track].get_clip(index); Mlt::Producer prod(pr); if (!prod.same_clip(*clip)) { qDebug() << "ERROR: Wrong clip at position " << i; delete pr; return false; } delete pr; } else { if (!isBlankAt(i)) { qDebug() << "ERROR: Found clip when blank was required at position " << i; return false; } } } // We now check compositions positions if (m_allCompositions.size() != m_compoPos.size()) { qDebug() << "Error: the number of compositions position doesn't match number of compositions"; return false; } for (const auto &compo : m_allCompositions) { int pos = compo.second->getPosition(); if (m_compoPos.count(pos) == 0) { qDebug() << "Error: the position of composition " << compo.first << " is not properly stored"; return false; } if (m_compoPos[pos] != compo.first) { qDebug() << "Error: found composition" << m_compoPos[pos] << "instead of " << compo.first << "at position" << pos; return false; } } for (auto it = m_compoPos.begin(); it != m_compoPos.end(); ++it) { int compoId = it->second; int cur_in = m_allCompositions[compoId]->getPosition(); Q_ASSERT(cur_in == it->first); int cur_out = cur_in + m_allCompositions[compoId]->getPlaytime() - 1; ++it; if (it != m_compoPos.end()) { int next_compoId = it->second; int next_in = m_allCompositions[next_compoId]->getPosition(); int next_out = next_in + m_allCompositions[next_compoId]->getPlaytime() - 1; if (next_in <= cur_out) { qDebug() << "Error: found collision between composition " << compoId << "[ " << cur_in << ", " << cur_out << "] and " << next_compoId << "[ " << next_in << ", " << next_out << "]"; return false; } } --it; } return true; } std::pair TrackModel::getClipIndexAt(int position) { READ_LOCK(); for (int j = 0; j < 2; j++) { if (!m_playlists[j].is_blank_at(position)) { return {j, m_playlists[j].get_clip_index_at(position)}; } } Q_ASSERT(false); return {-1, -1}; } bool TrackModel::isBlankAt(int position) { READ_LOCK(); return m_playlists[0].is_blank_at(position) && m_playlists[1].is_blank_at(position); } int TrackModel::getBlankStart(int position) { READ_LOCK(); int result = 0; for (auto &m_playlist : m_playlists) { if (m_playlist.count() == 0) { break; } if (!m_playlist.is_blank_at(position)) { result = position; break; } int clip_index = m_playlist.get_clip_index_at(position); int start = m_playlist.clip_start(clip_index); if (start > result) { result = start; } } return result; } int TrackModel::getBlankEnd(int position, int track) { READ_LOCK(); // Q_ASSERT(m_playlists[track].is_blank_at(position)); if (!m_playlists[track].is_blank_at(position)) { return position; } int clip_index = m_playlists[track].get_clip_index_at(position); int count = m_playlists[track].count(); if (clip_index < count) { int blank_start = m_playlists[track].clip_start(clip_index); int blank_length = m_playlists[track].clip_length(clip_index); return blank_start + blank_length; } return INT_MAX; } int TrackModel::getBlankEnd(int position) { READ_LOCK(); int end = INT_MAX; for (int j = 0; j < 2; j++) { end = std::min(getBlankEnd(position, j), end); } return end; } Fun TrackModel::requestCompositionResize_lambda(int compoId, int in, int out, bool logUndo) { QWriteLocker locker(&m_lock); int compo_position = m_allCompositions[compoId]->getPosition(); Q_ASSERT(m_compoPos.count(compo_position) > 0); Q_ASSERT(m_compoPos[compo_position] == compoId); int old_in = compo_position; int old_out = old_in + m_allCompositions[compoId]->getPlaytime() - 1; qDebug() << "compo resize " << compoId << in << "-" << out << " / " << old_in << "-" << old_out; if (out == -1) { out = in + old_out - old_in; } auto update_snaps = [old_in, old_out, logUndo, 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 + 1); ptr->m_snaps->addPoint(new_in); ptr->m_snaps->addPoint(new_out); ptr->checkRefresh(old_in, old_out); ptr->checkRefresh(new_in, new_out); if (logUndo) { ptr->invalidateZone(old_in, old_out); ptr->invalidateZone(new_in, new_out); } // ptr->adjustAssetRange(compoId, new_in, new_out); } else { qDebug() << "Error : Composition resize failed because parent timeline is not available anymore"; Q_ASSERT(false); } }; if (in == compo_position && (out == -1 || out == old_out)) { return []() { qDebug() << "//// NO MOVE PERFORMED\n!!!!!!!!!!!!!!!!!!!!!!!!!!"; return true; }; } // temporary remove of current compo to check collisions qDebug() << "// CURRENT COMPOSITIONS ----\n" << m_compoPos << "\n--------------"; m_compoPos.erase(compo_position); bool intersecting = hasIntersectingComposition(in, out); // put it back m_compoPos[compo_position] = compoId; if (intersecting) { return []() { qDebug() << "//// FALSE MOVE PERFORMED\n!!!!!!!!!!!!!!!!!!!!!!!!!!"; return false; }; } return [in, out, compoId, update_snaps, this]() { m_compoPos.erase(m_allCompositions[compoId]->getPosition()); m_allCompositions[compoId]->setInOut(in, out); update_snaps(in, out + 1); m_compoPos[m_allCompositions[compoId]->getPosition()] = compoId; return true; }; } bool TrackModel::requestCompositionInsertion(int compoId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (isLocked()) { return false; } auto operation = requestCompositionInsertion_lambda(compoId, position, updateView, finalMove); if (operation()) { auto reverse = requestCompositionDeletion_lambda(compoId, updateView, finalMove); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } bool TrackModel::requestCompositionDeletion(int compoId, bool updateView, bool finalMove, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (isLocked()) { return false; } Q_ASSERT(m_allCompositions.count(compoId) > 0); auto old_composition = m_allCompositions[compoId]; int old_position = old_composition->getPosition(); Q_ASSERT(m_compoPos.count(old_position) > 0); Q_ASSERT(m_compoPos[old_position] == compoId); auto operation = requestCompositionDeletion_lambda(compoId, updateView, finalMove); if (operation()) { auto reverse = requestCompositionInsertion_lambda(compoId, old_position, updateView, finalMove); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } Fun TrackModel::requestCompositionDeletion_lambda(int compoId, bool updateView, bool finalMove) { QWriteLocker locker(&m_lock); // Find index of clip int clip_position = m_allCompositions[compoId]->getPosition(); int old_in = clip_position; int old_out = old_in + m_allCompositions[compoId]->getPlaytime(); return [compoId, old_in, old_out, updateView, finalMove, this]() { int old_clip_index = getRowfromComposition(compoId); auto ptr = m_parent.lock(); if (updateView) { ptr->_beginRemoveRows(ptr->makeTrackIndexFromID(getId()), old_clip_index, old_clip_index); ptr->_endRemoveRows(); } m_allCompositions[compoId]->setCurrentTrackId(-1); m_allCompositions.erase(compoId); m_compoPos.erase(old_in); ptr->m_snaps->removePoint(old_in); ptr->m_snaps->removePoint(old_out); if (finalMove) { ptr->invalidateZone(old_in, old_out); } return true; }; } int TrackModel::getCompositionByRow(int row) const { READ_LOCK(); if (row < (int)m_allClips.size()) { return -1; } Q_ASSERT(row <= (int)m_allClips.size() + (int)m_allCompositions.size()); auto it = m_allCompositions.cbegin(); std::advance(it, row - (int)m_allClips.size()); return (*it).first; } int TrackModel::getCompositionsCount() const { READ_LOCK(); return (int)m_allCompositions.size(); } Fun TrackModel::requestCompositionInsertion_lambda(int compoId, int position, bool updateView, bool finalMove) { QWriteLocker locker(&m_lock); bool intersecting = true; if (auto ptr = m_parent.lock()) { intersecting = hasIntersectingComposition(position, position + ptr->getCompositionPlaytime(compoId) - 1); } else { qDebug() << "Error : Composition Insertion failed because timeline is not available anymore"; } if (!intersecting) { return [compoId, this, position, updateView, finalMove]() { if (auto ptr = m_parent.lock()) { std::shared_ptr composition = ptr->getCompositionPtr(compoId); m_allCompositions[composition->getId()] = composition; // store clip // update clip position and track composition->setCurrentTrackId(getId()); int new_in = position; int new_out = new_in + composition->getPlaytime(); composition->setInOut(new_in, new_out - 1); if (updateView) { int composition_index = getRowfromComposition(composition->getId()); ptr->_beginInsertRows(ptr->makeTrackIndexFromID(composition->getCurrentTrackId()), composition_index, composition_index); ptr->_endInsertRows(); } ptr->m_snaps->addPoint(new_in); ptr->m_snaps->addPoint(new_out); m_compoPos[new_in] = composition->getId(); if (finalMove) { ptr->invalidateZone(new_in, new_out); } return true; } qDebug() << "Error : Composition Insertion failed because timeline is not available anymore"; return false; }; } return []() { return false; }; } bool TrackModel::hasIntersectingComposition(int in, int out) const { READ_LOCK(); auto it = m_compoPos.lower_bound(in); if (m_compoPos.empty()) { return false; } if (it != m_compoPos.end() && it->first <= out) { // compo at it intersects return true; } if (it == m_compoPos.begin()) { return false; } --it; int end = it->first + m_allCompositions.at(it->second)->getPlaytime() - 1; return end >= in; return false; } bool TrackModel::addEffect(const QString &effectId) { READ_LOCK(); return m_effectStack->appendEffect(effectId); } const QString TrackModel::effectNames() const { READ_LOCK(); return m_effectStack->effectNames(); } bool TrackModel::stackEnabled() const { READ_LOCK(); return m_effectStack->isStackEnabled(); } void TrackModel::setEffectStackEnabled(bool enable) { m_effectStack->setEffectStackEnabled(enable); } int TrackModel::trackDuration() { return m_track->get_length(); } bool TrackModel::isLocked() const { READ_LOCK(); return m_track->get_int("kdenlive:locked_track"); } bool TrackModel::isAudioTrack() const { return m_track->get_int("kdenlive:audio_track") == 1; } PlaylistState::ClipState TrackModel::trackType() const { return (m_track->get_int("kdenlive:audio_track") == 1 ? PlaylistState::AudioOnly : PlaylistState::VideoOnly); } bool TrackModel::isHidden() const { return m_track->get_int("hide") & 1; } bool TrackModel::isMute() const { return m_track->get_int("hide") & 2; } bool TrackModel::importEffects(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_effectStack->importEffects(std::move(service), trackType()); return true; } bool TrackModel::copyEffect(const std::shared_ptr &stackModel, int rowId) { QWriteLocker locker(&m_lock); return m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), isAudioTrack() ? PlaylistState::AudioOnly : PlaylistState::VideoOnly); } diff --git a/tests/regressions.cpp b/tests/regressions.cpp index c629847ec..ac85520e6 100644 --- a/tests/regressions.cpp +++ b/tests/regressions.cpp @@ -1,888 +1,955 @@ #include "test_utils.hpp" Mlt::Profile reg_profile; TEST_CASE("Regression") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(®_profile, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); TimelineModel::next_id = 0; undoStack->undo(); undoStack->redo(); undoStack->redo(); undoStack->undo(); QString binId0 = createProducer(reg_profile, "red", binModel); int c = ClipModel::construct(timeline, binId0, -1, PlaylistState::VideoOnly); timeline->m_allClips[c]->m_endlessResize = false; TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestClipMove(0, 1, 0)); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestItemResize(0, 16, false)); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestItemResize(0, 0, false) == -1); REQUIRE(timeline->getTrackById(1)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Regression2") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(®_profile, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); TimelineModel::next_id = 0; int dummy_id; undoStack->undo(); undoStack->undo(); undoStack->redo(); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 0, 10, dummy_id); timeline->m_allClips[dummy_id]->m_endlessResize = false; REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 2, 10, dummy_id); timeline->m_allClips[3]->m_endlessResize = false; REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { bool ok = timeline->requestClipMove(1, 0, 10); REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->redo(); timeline->m_allClips[3]->m_endlessResize = false; REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 0, false) == -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); int c = ClipModel::construct(timeline, binId0, -1, PlaylistState::VideoOnly); timeline->m_allClips[c]->m_endlessResize = false; } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 15, true) > -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { bool ok = timeline->requestClipMove(3, 0, 0); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 16, false) == -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 16, true) > -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 0, 1, dummy_id); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->redo(); binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } /* TEST_CASE("Regression 3") { Mlt::Profile profile; std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr timeline = TimelineItemModel::construct(new Mlt::Profile(), undoStack); TimelineModel::next_id = 0; int dummy_id; std::shared_ptr producer0 = std::make_shared(profile, "color", "red"); producer0->set("length", 20); producer0->set("out", 19); ClipModel::construct(timeline, producer0 ); { bool ok = timeline->requestTrackInsertion(-1, dummy_id); REQUIRE(ok); } TrackModel::construct(timeline); TrackModel::construct(timeline); std::shared_ptr producer1 = std::make_shared(profile, "color", "red"); producer1->set("length", 20); producer1->set("out", 19); ClipModel::construct(timeline, producer1 ); std::shared_ptr producer2 = std::make_shared(profile, "color", "red"); producer2->set("length", 20); producer2->set("out", 19); ClipModel::construct(timeline, producer2 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); std::shared_ptr producer3 = std::make_shared(profile, "color", "red"); producer3->set("length", 20); producer3->set("out", 19); ClipModel::construct(timeline, producer3 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer4 = std::make_shared(profile, "color", "red"); producer4->set("length", 20); producer4->set("out", 19); ClipModel::construct(timeline, producer4 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer5 = std::make_shared(profile, "color", "red"); producer5->set("length", 20); producer5->set("out", 19); ClipModel::construct(timeline, producer5 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer6 = std::make_shared(profile, "color", "red"); producer6->set("length", 20); producer6->set("out", 19); ClipModel::construct(timeline, producer6 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(0,1 ,10 ); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,2 ,12 ); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { auto group = {4, 0}; bool ok = timeline->requestClipsGroup(group); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,1 ,10 ); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,1 ,100 ); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(0,3 ,100 ); REQUIRE(ok); } std::shared_ptr producer7 = std::make_shared(profile, "color", "red"); producer7->set("length", 20); producer7->set("out", 19); ClipModel::construct(timeline, producer7 ); { bool ok = timeline->requestTrackInsertion(-1, dummy_id); REQUIRE(ok); } undoStack->undo(); { bool ok = timeline->requestClipMove(0,1 ,5 ); REQUIRE(ok); } { bool ok = timeline->requestTrackDeletion(1); REQUIRE(ok); } } TEST_CASE("Regression 4") { Mlt::Profile profile; std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr timeline = TimelineItemModel::construct(new Mlt::Profile(), undoStack); TimelineModel::next_id = 0; int dummy_id; timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); { std::shared_ptr producer = std::make_shared(profile, "color", "red"); producer->set("length", 62); producer->set("out", 61); timeline->requestClipInsertion(producer,10 ,453, dummy_id ); } timeline->requestClipMove(11,10 ,453, true, true ); { std::shared_ptr producer = std::make_shared(profile, "color", "red"); producer->set("length", 62); producer->set("out", 61); timeline->requestClipInsertion(producer,9 ,590, dummy_id ); } timeline->requestItemResize(11,62 ,true, false, true ); timeline->requestItemResize(11,62 ,true, true, true ); timeline->requestClipMove(11,10 ,507, true, true ); timeline->requestClipMove(12,10 ,583, false, false ); timeline->requestClipMove(12,9 ,521, true, true ); } */ TEST_CASE("FuzzBug1") { Logger::clear(); auto binModel = pCore->projectItemModel(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; { Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, -1, "", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, -1, "$$$", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { bool res = timeline_0->requestTrackDeletion(2); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { bool res = timeline_0->requestTrackDeletion(1); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "red", binModel, 20, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 0, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 20, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 40, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({5, 7}, true, GroupType::Normal); REQUIRE(res == 8); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({6, 7}, true, GroupType::Normal); REQUIRE(res == 9); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({6, 7}, false, GroupType::Normal); REQUIRE(res == 9); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("FuzzBug2") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; { Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "$", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "d", binModel, 0, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 0, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 30, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 60, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({3, 2}, true, GroupType::AVSplit); REQUIRE(res == -1); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("FuzzBug3") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; { Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); createProducerWithSound(reg_profile, binModel); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, 0, "0", true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 0, dummy_3, false, false, false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; } TEST_CASE("FuzzBug4") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; { Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "red", binModel, 2, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "blue", binModel, 20, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "gseen", binModel, 20, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, -1, "", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducerWithSound(reg_profile, binModel); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("5", 1, 3, dummy_3, true, true, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { bool res = timeline_0->requestGroupDeletion(2, false); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; } TEST_CASE("FuzzBug5") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; { Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, 0, "", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TimelineItemModel tim_1(®_profile, undoStack); Mock timMock_1(tim_1); auto timeline_1 = std::shared_ptr(&timMock_1.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_1, guideModel); Fake(Method(timMock_1, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); TrackModel::construct(timeline_0, -1, 0, "$", false); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); TrackModel::construct(timeline_1, -1, 0, "$", true); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); createProducerWithSound(reg_profile, binModel); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); TrackModel::construct(timeline_1, -1, -1, "$", false); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); TrackModel::construct(timeline_1, -1, -1, "$", true); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); { int dummy_3; bool res = timeline_1->requestClipInsertion("2", 5, 0, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); { int dummy_3; bool res = timeline_1->requestClipInsertion("2", 6, 20, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); { bool res = timeline_1->requestGroupMove(10, 9, 0, 0, false, false); REQUIRE(res == false); } REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); REQUIRE(timeline_1->checkConsistency()); } pCore->m_projectManager = nullptr; } + +TEST_CASE("FuzzBug6") +{ + auto binModel = pCore->projectItemModel(); + binModel->clean(); + std::shared_ptr undoStack = std::make_shared(nullptr); + std::shared_ptr guideModel = std::make_shared(undoStack); + TimelineModel::next_id = 0; + { + Mock pmMock; + When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); + ProjectManager &mocked = pmMock.get(); + pCore->m_projectManager = &mocked; + TimelineItemModel tim_0(®_profile, undoStack); + Mock timMock_0(tim_0); + auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); + TimelineItemModel::finishConstruct(timeline_0, guideModel); + Fake(Method(timMock_0, adjustAssetRange)); + REQUIRE(timeline_0->checkConsistency()); + undoStack->undo(); + REQUIRE(timeline_0->checkConsistency()); + undoStack->redo(); + REQUIRE(timeline_0->checkConsistency()); + TrackModel::construct(timeline_0, -1, 0, "$", false); + REQUIRE(timeline_0->checkConsistency()); + undoStack->undo(); + REQUIRE(timeline_0->checkConsistency()); + undoStack->redo(); + REQUIRE(timeline_0->checkConsistency()); + TimelineItemModel tim_1(®_profile, undoStack); + Mock timMock_1(tim_1); + auto timeline_1 = std::shared_ptr(&timMock_1.get(), [](...) {}); + TimelineItemModel::finishConstruct(timeline_1, guideModel); + Fake(Method(timMock_1, adjustAssetRange)); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->undo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->redo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + createProducer(reg_profile, "b", binModel, 20, true); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->undo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->redo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + { + int dummy_3; + bool res = timeline_0->requestClipInsertion("2", 1, -1, dummy_3, false, false, false); + REQUIRE(res == false); + } + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->undo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + undoStack->redo(); + REQUIRE(timeline_0->checkConsistency()); + REQUIRE(timeline_1->checkConsistency()); + } + pCore->m_projectManager = nullptr; +}