/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team 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 3 of the License, or (at your option) any later version. 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 "undo.h" #include "node/graph.h" #include Block* CreateSplitBlock(Block* block, rational point, QObject* parent = nullptr) { Block* copy = block->copy(); copy->set_length_and_media_in(block->length() - (point - block->in())); copy->setParent(parent); return copy; } Node* TakeNodeFromParentGraph(Node* n, QObject* new_parent = nullptr) { static_cast(n->parent())->TakeNode(n, new_parent); return n; } BlockResizeCommand::BlockResizeCommand(Block *block, rational new_length, QUndoCommand* parent) : QUndoCommand(parent), block_(block), old_length_(block->length()), new_length_(new_length) { } void BlockResizeCommand::redo() { block_->set_length(new_length_); } void BlockResizeCommand::undo() { block_->set_length(old_length_); } BlockResizeWithMediaInCommand::BlockResizeWithMediaInCommand(Block *block, rational new_length, QUndoCommand *parent) : QUndoCommand(parent), block_(block), old_length_(block->length()), new_length_(new_length) { } void BlockResizeWithMediaInCommand::redo() { block_->set_length_and_media_in(new_length_); } void BlockResizeWithMediaInCommand::undo() { block_->set_length_and_media_in(old_length_); } BlockSetMediaInCommand::BlockSetMediaInCommand(Block *block, rational new_media_in, QUndoCommand* parent) : QUndoCommand(parent), block_(block), old_media_in_(block->media_in()), new_media_in_(new_media_in) { } void BlockSetMediaInCommand::redo() { block_->set_media_in(new_media_in_); } void BlockSetMediaInCommand::undo() { block_->set_media_in(old_media_in_); } TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) : QUndoCommand(parent), track_(track), block_(block), before_(block->previous()), after_(block->next()) { } void TrackRippleRemoveBlockCommand::redo() { track_->RippleRemoveBlock(block_); } void TrackRippleRemoveBlockCommand::undo() { track_->InsertBlockBetweenBlocks(block_, before_, after_); } TrackInsertBlockBetweenBlocksCommand::TrackInsertBlockBetweenBlocksCommand(TrackOutput *track, Block *block, Block *before, Block *after, QUndoCommand *parent) : QUndoCommand(parent), track_(track), block_(block), before_(before), after_(after) { } void TrackInsertBlockBetweenBlocksCommand::redo() { track_->InsertBlockBetweenBlocks(block_, before_, after_); } void TrackInsertBlockBetweenBlocksCommand::undo() { track_->RippleRemoveBlock(block_); } TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, rational in, rational out, QUndoCommand *parent) : QUndoCommand(parent), track_(track), in_(in), out_(out), splice_(nullptr), trim_out_(nullptr), trim_in_(nullptr), insert_(nullptr) { } void TrackRippleRemoveAreaCommand::SetInsert(Block *insert) { insert_ = insert; } void TrackRippleRemoveAreaCommand::redo() { // Iterate through blocks determining which need trimming/removing/splitting foreach (Block* block, track_->Blocks()) { if (block->in() < in_ && block->out() > out_) { // The area entirely within this Block splice_ = block; // We don't need to do anything else here break; } else if (block->in() >= in_ && block->out() <= out_) { // This Block's is entirely within the area removed_blocks_.append(block); } else if (block->in() < in_ && block->out() >= in_) { // This Block's out point exceeds `in` trim_out_ = block; } else if (block->in() <= out_ && block->out() > out_) { // This Block's in point exceeds `out` trim_in_ = block; } } track_->BlockInvalidateCache(); // If we picked up a block to splice if (splice_ != nullptr) { // Split the block here Block* copy = CreateSplitBlock(splice_, out_); splice_original_length_ = splice_->length(); splice_->set_length(out_ - splice_->in()); track_->AddBlockToGraph(copy); Node::CopyInputs(splice_, copy); track_->InsertBlockAfter(copy, splice_); // Perform all further actions as if we were just trimming these clips trim_out_ = splice_; trim_in_ = copy; } // If we picked up a block to trim the in point of if (trim_in_ != nullptr) { trim_in_old_length_ = trim_in_->length(); trim_in_new_length_ = trim_in_->out() - out_; } // If we picked up a block to trim the out point of if (trim_out_ != nullptr) { trim_out_old_length_ = trim_out_->length(); trim_out_new_length_ = in_ - trim_out_->in(); } // If we picked up a block to trim the in point of if (trim_in_old_length_ != trim_in_new_length_) { trim_in_->set_length_and_media_in(trim_in_new_length_); } // Remove all blocks that are flagged for removal foreach (Block* remove_block, removed_blocks_) { track_->RippleRemoveBlock(remove_block); } // If we picked up a block to trim the out point of if (trim_out_old_length_ != trim_out_new_length_) { trim_out_->set_length(trim_out_new_length_); } // If we were given a block to insert, insert it here if (insert_ != nullptr) { track_->AddBlockToGraph(insert_); if (trim_out_ == nullptr) { // This is the start of the Sequence track_->PrependBlock(insert_); } else if (trim_in_ == nullptr) { // This is the end of the Sequence track_->AppendBlock(insert_); } else { // This is somewhere in the middle of the Sequence track_->InsertBlockBetweenBlocks(insert_, trim_out_, trim_in_); } } track_->UnblockInvalidateCache(); track_->InvalidateCache(in_, out_); } void TrackRippleRemoveAreaCommand::undo() { track_->BlockInvalidateCache(); // If we were given a block to insert, insert it here if (insert_ != nullptr) { track_->RippleRemoveBlock(insert_); } // If we picked up a block to trim the out point of if (trim_out_old_length_ != trim_out_new_length_) { trim_out_->set_length(trim_out_old_length_); } // Remove all blocks that are flagged for removal foreach (Block* remove_block, removed_blocks_) { if (trim_in_ == nullptr) { track_->AppendBlock(remove_block); } else { track_->InsertBlockBefore(remove_block, trim_in_); } } // If we picked up a block to trim the in point of if (trim_in_old_length_ != trim_in_new_length_) { trim_in_->set_length_and_media_in(trim_in_old_length_); } // If we're splicing, trim_in_ is a copy if (splice_ != nullptr) { track_->RippleRemoveBlock(trim_in_); // Remove node TakeNodeFromParentGraph(trim_in_, &memory_manager_); splice_->set_length(splice_original_length_); } track_->UnblockInvalidateCache(); track_->InvalidateCache(in_, out_); } TrackPlaceBlockCommand::TrackPlaceBlockCommand(TimelineOutput* timeline, int track, Block *block, rational in, QUndoCommand *parent) : TrackRippleRemoveAreaCommand(nullptr, in, 0, parent), // Out gets set correctly in redo() timeline_(timeline), track_index_(track), gap_(nullptr) { insert_ = block; insert_->setParent(&memory_manager_); } void TrackPlaceBlockCommand::redo() { added_track_count_ = 0; // Get track (or make it if necessary) while (track_index_ >= timeline_->Tracks().size()) { timeline_->AddTrack(); added_track_count_++; } track_ = timeline_->TrackAt(track_index_); append_ = (in_ >= track_->in()); // Check if the placement location is past the end of the timeline if (append_) { if (in_ > track_->in()) { // If so, insert a gap here gap_ = new GapBlock(); gap_->set_length(in_ - track_->in()); track_->AppendBlock(gap_); } track_->AppendBlock(insert_); } else { out_ = in_ + insert_->length(); // Place the Block at this point TrackRippleRemoveAreaCommand::redo(); } } void TrackPlaceBlockCommand::undo() { if (append_) { track_->RippleRemoveBlock(insert_); TakeNodeFromParentGraph(insert_, &memory_manager_); if (gap_ != nullptr) { track_->RippleRemoveBlock(gap_); delete TakeNodeFromParentGraph(gap_); } } else { TrackRippleRemoveAreaCommand::undo(); } for (;added_track_count_>0;added_track_count_--) { timeline_->RemoveTrack(); } } BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational point, QUndoCommand *parent) : QUndoCommand(parent), track_(track), block_(block), new_length_(point - block->in()), old_length_(block->length()) { Q_ASSERT(point > block_->in() && point < block_->out()); // Ensures that this block is deleted if this action is undone new_block_ = CreateSplitBlock(block_, point, &memory_manager_); } void BlockSplitCommand::redo() { track_->BlockInvalidateCache(); block_->set_length(new_length_); track_->AddBlockToGraph(new_block_); Node::CopyInputs(block_, new_block_); // Will re-parent new_block_ to the track's graph track_->InsertBlockAfter(new_block_, block_); track_->UnblockInvalidateCache(); } void BlockSplitCommand::undo() { track_->BlockInvalidateCache(); block_->set_length(old_length_); track_->RippleRemoveBlock(new_block_); TakeNodeFromParentGraph(new_block_, &memory_manager_); track_->UnblockInvalidateCache(); } TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational point, QUndoCommand *parent) : QUndoCommand(parent) { // Find Block that contains this time for (int i=0;iBlocks().size();i++) { Block* b = track->Blocks().at(i); if (b->out() == point) { // This time is between blocks, no split needs to occur return; } else if (b->in() < point && b->out() > point) { // We found the Block, split it new BlockSplitCommand(track, b, point, this); return; } } } TrackReplaceBlockCommand::TrackReplaceBlockCommand(TrackOutput* track, Block *old, Block *replace, QUndoCommand *parent) : QUndoCommand(parent), track_(track), old_(old), replace_(replace) { } void TrackReplaceBlockCommand::redo() { track_->ReplaceBlock(old_, replace_); } void TrackReplaceBlockCommand::undo() { track_->ReplaceBlock(replace_, old_); } TrackPrependBlockCommand::TrackPrependBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) : QUndoCommand(parent), track_(track), block_(block) { } void TrackPrependBlockCommand::redo() { track_->PrependBlock(block_); } void TrackPrependBlockCommand::undo() { track_->RippleRemoveBlock(block_); }