timeline: remove transitions when removing clips

Fixes #1224
This commit is contained in:
itsmattkc
2021-04-26 20:29:21 +10:00
parent 67f48bc37c
commit e2a590da26
6 changed files with 564 additions and 245 deletions
-10
View File
@@ -249,21 +249,11 @@ void TransitionBlock::InputConnectedEvent(const QString &input, int element, con
if (input == kOutBlockInput) {
// If node is not a block, this will just be null
if ((connected_out_block_ = dynamic_cast<Block*>(output.node()))) {
Q_ASSERT(!dynamic_cast<TransitionBlock*>(connected_out_block_)
&& !connected_out_block_->out_transition()
&& connected_out_block_ == this->previous());
connected_out_block_->set_out_transition(this);
}
} else if (input == kInBlockInput) {
// If node is not a block, this will just be null
if ((connected_in_block_ = dynamic_cast<Block*>(output.node()))) {
Q_ASSERT(!dynamic_cast<TransitionBlock*>(connected_in_block_)
&& !connected_in_block_->in_transition()
&& connected_in_block_ == this->next());
connected_in_block_->set_in_transition(this);
}
}
+172
View File
@@ -200,4 +200,176 @@ void BlockTrimCommand::prep()
}
}
void TrackReplaceBlockWithGapCommand::redo()
{
// Determine if this block is connected to any transitions that should also be removed by this operation
if (transition_remove_commands_.isEmpty()) {
CreateRemoveTransitionCommandIfNecessary(false);
CreateRemoveTransitionCommandIfNecessary(true);
}
for (auto it=transition_remove_commands_.cbegin(); it!=transition_remove_commands_.cend(); it++) {
(*it)->redo();
}
if (block_->next()) {
track_->BeginOperation();
// Invalidate the range inhabited by this block
TimeRange invalidate_range(block_->in(), block_->out());
// Block has a next, which means it's NOT at the end of the sequence and thus requires a gap
rational new_gap_length = block_->length();
Block* previous = block_->previous();
Block* next = block_->next();
bool previous_is_a_gap = dynamic_cast<GapBlock*>(previous);
bool next_is_a_gap = dynamic_cast<GapBlock*>(next);
if (previous_is_a_gap && next_is_a_gap) {
// Clip is preceded and followed by a gap, so we'll merge the two
existing_gap_ = static_cast<GapBlock*>(previous);
existing_merged_gap_ = static_cast<GapBlock*>(next);
new_gap_length += existing_merged_gap_->length();
track_->RippleRemoveBlock(existing_merged_gap_);
existing_merged_gap_->setParent(&memory_manager_);
} else if (previous_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock*>(previous);
} else if (next_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock*>(next);
}
if (existing_gap_) {
// Extend an existing gap
new_gap_length += existing_gap_->length();
existing_gap_->set_length_and_media_out(new_gap_length);
track_->RippleRemoveBlock(block_);
existing_gap_precedes_ = (existing_gap_ == previous);
} else {
// No gap exists to fill this space, create a new one and swap it in
if (!our_gap_) {
our_gap_ = new GapBlock();
our_gap_->set_length_and_media_out(new_gap_length);
}
our_gap_->setParent(track_->parent());
track_->ReplaceBlock(block_, our_gap_);
if (!position_command_) {
position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, our_gap_->index(), track_->Blocks().size(), true);
}
position_command_->redo();
}
track_->EndOperation();
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
} else {
// Block is at the end of the track, simply remove it
// Determine if it's proceeded by a gap, and remove that gap if so
Block* preceding = block_->previous();
if (dynamic_cast<GapBlock*>(preceding)) {
track_->RippleRemoveBlock(preceding);
preceding->setParent(&memory_manager_);
existing_merged_gap_ = static_cast<GapBlock*>(preceding);
}
// Remove block in question
track_->RippleRemoveBlock(block_);
}
}
void TrackReplaceBlockWithGapCommand::undo()
{
if (our_gap_ || existing_gap_) {
track_->BeginOperation();
if (our_gap_) {
// We made this gap, simply swap our gap back
track_->ReplaceBlock(our_gap_, block_);
our_gap_->setParent(&memory_manager_);
position_command_->undo();
} else {
// If we're here, assume that we extended an existing gap
rational original_gap_length = existing_gap_->length() - block_->length();
// If we merged two gaps together, restore the second one now
if (existing_merged_gap_) {
original_gap_length -= existing_merged_gap_->length();
existing_merged_gap_->setParent(track_->parent());
track_->InsertBlockAfter(existing_merged_gap_, existing_gap_);
existing_merged_gap_ = nullptr;
}
// Restore original block
if (existing_gap_precedes_) {
track_->InsertBlockAfter(block_, existing_gap_);
} else {
track_->InsertBlockBefore(block_, existing_gap_);
}
// Restore gap's original length
existing_gap_->set_length_and_media_out(original_gap_length);
existing_gap_ = nullptr;
}
track_->EndOperation();
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
} else {
// Our gap and existing gap were both null, our block must have been at the end and thus
// required no gap extension/replacement
// However, we may have removed an unnecessary gap that preceded it
if (existing_merged_gap_) {
existing_merged_gap_->setParent(track_->parent());
track_->AppendBlock(existing_merged_gap_);
existing_merged_gap_ = nullptr;
}
// Restore block
track_->AppendBlock(block_);
}
for (auto it=transition_remove_commands_.crbegin(); it!=transition_remove_commands_.crend(); it++) {
(*it)->undo();
}
}
void TrackReplaceBlockWithGapCommand::CreateRemoveTransitionCommandIfNecessary(bool next)
{
Block* relevant_block;
if (next) {
relevant_block = block_->next();
} else {
relevant_block = block_->previous();
}
TransitionBlock* transition_cast_test = dynamic_cast<TransitionBlock*>(relevant_block);
if (transition_cast_test) {
if ((next && transition_cast_test->connected_out_block() == block_ && !transition_cast_test->connected_in_block())
|| (!next && transition_cast_test->connected_in_block() == block_ && !transition_cast_test->connected_out_block())) {
TransitionRemoveCommand* command = new TransitionRemoveCommand(transition_cast_test, true);
transition_remove_commands_.append(command);
}
}
}
}
+114 -227
View File
@@ -1484,6 +1484,114 @@ private:
};
class TransitionRemoveCommand : public UndoCommand {
public:
TransitionRemoveCommand(TransitionBlock* block, bool remove_from_graph) :
block_(block),
remove_from_graph_(remove_from_graph),
remove_command_(nullptr)
{
}
virtual Project* GetRelevantProject() const override
{
return track_->project();
}
virtual void redo() override
{
track_ = block_->track();
out_block_ = block_->connected_out_block();
in_block_ = block_->connected_in_block();
Q_ASSERT(out_block_ || in_block_);
track_->BeginOperation();
TimeRange invalidate_range(block_->in(), block_->out());
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() + block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() + block_->out_offset());
}
if (in_block_) {
Node::DisconnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput));
}
if (out_block_) {
Node::DisconnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput));
}
track_->RippleRemoveBlock(block_);
track_->EndOperation();
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
if (remove_from_graph_) {
if (!remove_command_) {
remove_command_ = CreateRemoveCommand(block_);
}
remove_command_->redo();
}
}
virtual void undo() override
{
if (remove_from_graph_) {
remove_command_->undo();
}
track_->BeginOperation();
if (in_block_) {
track_->InsertBlockBefore(block_, in_block_);
} else {
track_->InsertBlockAfter(block_, out_block_);
}
if (in_block_) {
Node::ConnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput));
}
if (out_block_) {
Node::ConnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput));
}
// These if statements must be separated because in_offset and out_offset report different things
// if only one block is connected vs two. So we have to connect the blocks first before we have
// an accurate return value from these offset functions.
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() - block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() - block_->out_offset());
}
track_->EndOperation();
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
}
private:
TransitionBlock* block_;
Track* track_;
Block* out_block_;
Block* in_block_;
bool remove_from_graph_;
UndoCommand* remove_command_;
};
class TrackReplaceBlockWithGapCommand : public UndoCommand {
public:
TrackReplaceBlockWithGapCommand(Track* track, Block* block) :
@@ -1506,145 +1614,13 @@ public:
return block_->project();
}
virtual void redo() override
{
if (block_->next()) {
track_->BeginOperation();
virtual void redo() override;
// Invalidate the range inhabited by this block
TimeRange invalidate_range(block_->in(), block_->out());
// Block has a next, which means it's NOT at the end of the sequence and thus requires a gap
rational new_gap_length = block_->length();
Block* previous = block_->previous();
Block* next = block_->next();
bool previous_is_a_gap = dynamic_cast<GapBlock*>(previous);
bool next_is_a_gap = dynamic_cast<GapBlock*>(next);
if (previous_is_a_gap && next_is_a_gap) {
// Clip is preceded and followed by a gap, so we'll merge the two
existing_gap_ = static_cast<GapBlock*>(previous);
existing_merged_gap_ = static_cast<GapBlock*>(next);
new_gap_length += existing_merged_gap_->length();
track_->RippleRemoveBlock(existing_merged_gap_);
existing_merged_gap_->setParent(&memory_manager_);
} else if (previous_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock*>(previous);
} else if (next_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock*>(next);
}
if (existing_gap_) {
// Extend an existing gap
new_gap_length += existing_gap_->length();
existing_gap_->set_length_and_media_out(new_gap_length);
track_->RippleRemoveBlock(block_);
existing_gap_precedes_ = (existing_gap_ == previous);
} else {
// No gap exists to fill this space, create a new one and swap it in
if (!our_gap_) {
our_gap_ = new GapBlock();
our_gap_->set_length_and_media_out(new_gap_length);
}
our_gap_->setParent(track_->parent());
track_->ReplaceBlock(block_, our_gap_);
if (!position_command_) {
position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, our_gap_->index(), track_->Blocks().size(), true);
}
position_command_->redo();
}
track_->EndOperation();
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
} else {
// Block is at the end of the track, simply remove it
// Determine if it's proceeded by a gap, and remove that gap if so
Block* preceding = block_->previous();
if (dynamic_cast<GapBlock*>(preceding)) {
track_->RippleRemoveBlock(preceding);
preceding->setParent(&memory_manager_);
existing_merged_gap_ = static_cast<GapBlock*>(preceding);
}
// Remove block in question
track_->RippleRemoveBlock(block_);
}
}
virtual void undo() override
{
if (our_gap_ || existing_gap_) {
track_->BeginOperation();
if (our_gap_) {
// We made this gap, simply swap our gap back
track_->ReplaceBlock(our_gap_, block_);
our_gap_->setParent(&memory_manager_);
position_command_->undo();
} else {
// If we're here, assume that we extended an existing gap
rational original_gap_length = existing_gap_->length() - block_->length();
// If we merged two gaps together, restore the second one now
if (existing_merged_gap_) {
original_gap_length -= existing_merged_gap_->length();
existing_merged_gap_->setParent(track_->parent());
track_->InsertBlockAfter(existing_merged_gap_, existing_gap_);
existing_merged_gap_ = nullptr;
}
// Restore original block
if (existing_gap_precedes_) {
track_->InsertBlockAfter(block_, existing_gap_);
} else {
track_->InsertBlockBefore(block_, existing_gap_);
}
// Restore gap's original length
existing_gap_->set_length_and_media_out(original_gap_length);
existing_gap_ = nullptr;
}
track_->EndOperation();
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
} else {
// Our gap and existing gap were both null, our block must have been at the end and thus
// required no gap extension/replacement
// However, we may have removed an unnecessary gap that preceded it
if (existing_merged_gap_) {
existing_merged_gap_->setParent(track_->parent());
track_->AppendBlock(existing_merged_gap_);
existing_merged_gap_ = nullptr;
}
// Restore block
track_->AppendBlock(block_);
}
}
virtual void undo() override;
private:
void CreateRemoveTransitionCommandIfNecessary(bool next);
Track* track_;
Block* block_;
@@ -1657,6 +1633,8 @@ private:
QObject memory_manager_;
QVector<TransitionRemoveCommand*> transition_remove_commands_;
};
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
@@ -2193,97 +2171,6 @@ private:
};
class TransitionRemoveCommand : public UndoCommand {
public:
TransitionRemoveCommand(TransitionBlock* block) :
block_(block)
{
}
virtual Project* GetRelevantProject() const override
{
return track_->project();
}
virtual void redo() override
{
track_ = block_->track();
out_block_ = block_->connected_out_block();
in_block_ = block_->connected_in_block();
Q_ASSERT(out_block_ || in_block_);
track_->BeginOperation();
TimeRange invalidate_range(block_->in(), block_->out());
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() + block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() + block_->out_offset());
}
if (in_block_) {
Node::DisconnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput));
}
if (out_block_) {
Node::DisconnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput));
}
track_->RippleRemoveBlock(block_);
track_->EndOperation();
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
}
virtual void undo() override
{
track_->BeginOperation();
if (in_block_) {
track_->InsertBlockBefore(block_, in_block_);
} else {
track_->InsertBlockAfter(block_, out_block_);
}
if (in_block_) {
Node::ConnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput));
}
if (out_block_) {
Node::ConnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput));
}
// These if statements must be separated because in_offset and out_offset report different things
// if only one block is connected vs two. So we have to connect the blocks first before we have
// an accurate return value from these offset functions.
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() - block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() - block_->out_offset());
}
track_->EndOperation();
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
}
private:
TransitionBlock* block_;
Track* track_;
Block* out_block_;
Block* in_block_;
};
}
#endif // TIMELINEUNDOABLE_H
+1 -3
View File
@@ -479,9 +479,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
// For transitions, remove them but extend their attached blocks to fill their place
foreach (TransitionBlock* transition, transitions_to_delete) {
command->add_child(new TransitionRemoveCommand(transition));
command->add_child(new NodeRemoveWithExclusiveDependenciesAndDisconnect(transition));
command->add_child(new TransitionRemoveCommand(transition, true));
}
// Replace clips with gaps (effectively deleting them)
+3 -3
View File
@@ -16,14 +16,14 @@
function(olive_add_test GROUP NAME SOURCE)
file(READ "${SOURCE}" TEST_FILE_CONTENT)
string(REGEX MATCHALL "OLIVE_ADD_TEST\(.[A-Za-z0-9]+\)" TEST_FUNCTIONS ${TEST_FILE_CONTENT})
string(REGEX MATCHALL "OLIVE_ADD_TEST\(.[A-Za-z0-9_]+\)" TEST_FUNCTIONS ${TEST_FILE_CONTENT})
set(TEST_BODY "int main(int argc, char** argv)\n{\n")
set(TEST_INDEX 1)
list(LENGTH TEST_FUNCTIONS TEST_COUNT)
foreach (TEST_FUNC ${TEST_FUNCTIONS})
string(REPLACE "OLIVE_ADD_TEST(" "" TEST_FUNC "${TEST_FUNC}")
string(APPEND TEST_BODY " std::cout << \"[${TEST_INDEX}/${TEST_COUNT}] ${GROUP} - ${TEST_FUNC}\" << std::endl;\n")
string(APPEND TEST_BODY " if (!olive::Test${TEST_FUNC}()) return 1;\n")
string(APPEND TEST_BODY " std::cout << \"[${TEST_INDEX}/${TEST_COUNT}] ${GROUP} - ${TEST_FUNC}\";\n")
string(APPEND TEST_BODY " if (olive::Test${TEST_FUNC}()) {std::cout << \" - PASSED\" << std::endl;}else{std::cout << \" - FAILED\" << std::endl;return 1;\n}\n")
MATH(EXPR TEST_INDEX "${TEST_INDEX}+1")
endforeach()
string(APPEND TEST_BODY " return 0;\n}")
+274 -2
View File
@@ -20,6 +20,7 @@
#include "core.h"
#include "node/block/clip/clip.h"
#include "node/block/transition/crossdissolve/crossdissolvetransition.h"
#include "node/math/math/math.h"
#include "node/math/merge/merge.h"
#include "node/project/project.h"
@@ -119,12 +120,12 @@ OLIVE_ADD_TEST(Trim)
ClipBlock* block1 = new ClipBlock();
block1->set_length_and_media_out(2);
project.setParent(block1);
block1->setParent(&project);
track->AppendBlock(block1);
ClipBlock* block2 = new ClipBlock();
block2->set_length_and_media_out(2);
project.setParent(block2);
block2->setParent(&project);
track->AppendBlock(block2);
// There should be two blocks right now
@@ -216,4 +217,275 @@ OLIVE_ADD_TEST(Trim)
OLIVE_TEST_END;
}
OLIVE_ADD_TEST(ReplaceBlockWithGap_ClipsOnly)
{
TIMELINE_TEST_START;
// Create a track that goes clip -> clip -> clip
sequence.add_default_nodes();
Track* track = sequence.track_list(Track::kVideo)->GetTracks().first();
ClipBlock* a = new ClipBlock();
a->setParent(&project);
track->AppendBlock(a);
ClipBlock* b = new ClipBlock();
b->setParent(&project);
track->AppendBlock(b);
ClipBlock* c = new ClipBlock();
c->setParent(&project);
track->AppendBlock(c);
{
// Replace clip C with a gap
TrackReplaceBlockWithGapCommand command(track, c);
command.redo();
// Clip should be removed without any gap actually taking its place, since the clip is at the
// end of the track
OLIVE_ASSERT(track->Blocks().size() == 2);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 3);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
}
{
// Replace clip B with a gap
TrackReplaceBlockWithGapCommand command(track, b);
command.redo();
// B should be replaced with a gap
OLIVE_ASSERT(track->Blocks().size() == 3);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) != b);
OLIVE_ASSERT(dynamic_cast<GapBlock*>(track->Blocks().at(1)));
OLIVE_ASSERT(track->Blocks().at(1)->length() == b->length());
OLIVE_ASSERT(track->Blocks().at(2) == c);
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 3);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
}
OLIVE_TEST_END;
}
OLIVE_ADD_TEST(ReplaceBlockWithGap_ClipsAndGaps)
{
TIMELINE_TEST_START;
// Create a track that goes clip -> gap -> clip -> clip -> gap -> clip
sequence.add_default_nodes();
Track* track = sequence.track_list(Track::kVideo)->GetTracks().first();
ClipBlock* a = new ClipBlock();
a->setParent(&project);
track->AppendBlock(a);
GapBlock* b = new GapBlock();
b->setParent(&project);
track->AppendBlock(b);
ClipBlock* c = new ClipBlock();
c->setParent(&project);
track->AppendBlock(c);
GapBlock* d = new GapBlock();
d->setParent(&project);
track->AppendBlock(d);
ClipBlock* e = new ClipBlock();
e->setParent(&project);
track->AppendBlock(e);
{
// Replace clip E with a gap
TrackReplaceBlockWithGapCommand command(track, e);
command.redo();
// Both clips D and E should be removed because this command should remove any trailing gaps
OLIVE_ASSERT(track->Blocks().size() == 3);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
// Test undo
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 5);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
OLIVE_ASSERT(track->Blocks().at(3) == d);
OLIVE_ASSERT(track->Blocks().at(4) == e);
}
{
// Replace clip A with a gap
rational original_length_of_a = a->length();
rational original_length_of_b = b->length();
TrackReplaceBlockWithGapCommand command(track, a);
command.redo();
// A should be removed and B should take its place
OLIVE_ASSERT(track->Blocks().size() == 4);
OLIVE_ASSERT(track->Blocks().at(0) == b);
OLIVE_ASSERT(track->Blocks().at(1) == c);
OLIVE_ASSERT(track->Blocks().at(2) == d);
OLIVE_ASSERT(track->Blocks().at(3) == e);
OLIVE_ASSERT(b->length() == original_length_of_a + original_length_of_b);
// Test undo
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 5);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
OLIVE_ASSERT(track->Blocks().at(3) == d);
OLIVE_ASSERT(track->Blocks().at(4) == e);
OLIVE_ASSERT(a->length() == original_length_of_a);
OLIVE_ASSERT(b->length() == original_length_of_b);
}
{
// Replace clip C with a gap
rational original_length_of_b = b->length();
rational original_length_of_c = c->length();
rational original_length_of_d = d->length();
TrackReplaceBlockWithGapCommand command(track, c);
command.redo();
// C and D should be removed, and B should take both of their places
OLIVE_ASSERT(track->Blocks().size() == 3);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == e);
OLIVE_ASSERT(b->length() == original_length_of_b + original_length_of_c + original_length_of_d);
// Test undo
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 5);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
OLIVE_ASSERT(track->Blocks().at(3) == d);
OLIVE_ASSERT(track->Blocks().at(4) == e);
OLIVE_ASSERT(b->length() == original_length_of_b);
OLIVE_ASSERT(c->length() == original_length_of_c);
OLIVE_ASSERT(d->length() == original_length_of_d);
}
{
// Add a fourth clip at the end of the track
ClipBlock* f = new ClipBlock();
f->setParent(&project);
track->AppendBlock(f);
// Try replacing E with a block again
TrackReplaceBlockWithGapCommand command(track, e);
rational original_length_of_d = d->length();
rational original_length_of_e = e->length();
command.redo();
// E should be removed and D should have taken its place
OLIVE_ASSERT(track->Blocks().size() == 5);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
OLIVE_ASSERT(track->Blocks().at(3) == d);
OLIVE_ASSERT(track->Blocks().at(4) == f);
OLIVE_ASSERT(d->length() == original_length_of_d + original_length_of_e);
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 6);
OLIVE_ASSERT(track->Blocks().at(0) == a);
OLIVE_ASSERT(track->Blocks().at(1) == b);
OLIVE_ASSERT(track->Blocks().at(2) == c);
OLIVE_ASSERT(track->Blocks().at(3) == d);
OLIVE_ASSERT(track->Blocks().at(4) == e);
OLIVE_ASSERT(track->Blocks().at(5) == f);
OLIVE_ASSERT(d->length() == original_length_of_d);
OLIVE_ASSERT(e->length() == original_length_of_e);
}
OLIVE_TEST_END;
}
#define UsingTransition CrossDissolveTransition
OLIVE_ADD_TEST(ReplaceBlockWithGap_ClipsAndTransitions)
{
TIMELINE_TEST_START;
// Create a track that goes clip -> gap -> clip -> clip -> gap -> clip
sequence.add_default_nodes();
Track* track = sequence.track_list(Track::kVideo)->GetTracks().first();
UsingTransition* a_in = new UsingTransition();
a_in->setParent(&project);
track->AppendBlock(a_in);
ClipBlock* a = new ClipBlock();
a->setParent(&project);
track->AppendBlock(a);
UsingTransition* a_to_b = new UsingTransition();
a_to_b->setParent(&project);
track->AppendBlock(a_to_b);
ClipBlock* b = new ClipBlock();
b->setParent(&project);
track->AppendBlock(b);
UsingTransition* b_out = new UsingTransition();
b_out->setParent(&project);
track->AppendBlock(b_out);
Node::ConnectEdge(a, NodeInput(a_in, UsingTransition::kInBlockInput));
Node::ConnectEdge(a, NodeInput(a_to_b, UsingTransition::kOutBlockInput));
Node::ConnectEdge(b, NodeInput(a_to_b, UsingTransition::kInBlockInput));
Node::ConnectEdge(b, NodeInput(b_out, UsingTransition::kOutBlockInput));
{
// Replace A with gap
TrackReplaceBlockWithGapCommand command(track, a);
command.redo();
// A should be replaced with a gap and so should A_IN since A was the only clip connected to it.
// Also A_TO_B should only be connected to B now
OLIVE_ASSERT(track->Blocks().size() == 4);
OLIVE_ASSERT(dynamic_cast<GapBlock*>(track->Blocks().at(0)));
OLIVE_ASSERT(track->Blocks().at(1) == a_to_b);
OLIVE_ASSERT(track->Blocks().at(2) == b);
OLIVE_ASSERT(track->Blocks().at(3) == b_out);
command.undo();
OLIVE_ASSERT(track->Blocks().size() == 5);
OLIVE_ASSERT(track->Blocks().at(0) == a_in);
OLIVE_ASSERT(track->Blocks().at(1) == a);
OLIVE_ASSERT(track->Blocks().at(2) == a_to_b);
OLIVE_ASSERT(track->Blocks().at(3) == b);
OLIVE_ASSERT(track->Blocks().at(4) == b_out);
}
OLIVE_TEST_END;
}
}