/*** 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 "node/block/transition/transition.h" Block* CreateSplitBlock(Block* block, rational point, QObject* parent = nullptr) { Block* copy = static_cast(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()) { } void TrackRippleRemoveBlockCommand::redo() { track_->RippleRemoveBlock(block_); } void TrackRippleRemoveBlockCommand::undo() { if (before_) { track_->InsertBlockAfter(block_, before_); } else { track_->AppendBlock(block_); } } 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_->InsertBlockAfter(block_, before_); } 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()); static_cast(track_->parent())->AddNode(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); // FIXME: Delete blocks from graph and restore them in undo } // 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_) { if (!trim_out_) { // This is the start of the Sequence track_->PrependBlock(insert_); } else if (!trim_in_) { // This is the end of the Sequence track_->AppendBlock(insert_); } else { // This is somewhere in the middle of the Sequence track_->InsertBlockAfter(insert_, trim_out_); } } 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(TrackList *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; } 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_->track_length()); // Check if the placement location is past the end of the timeline if (append_) { if (in_ > track_->track_length()) { // If so, insert a gap here gap_ = new GapBlock(); gap_->set_length(in_ - track_->track_length()); static_cast(track_->parent())->AddNode(gap_); 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_); 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_); // Determine if the block outputs to an "out" transition foreach (NodeEdgePtr edge, block_->output()->edges()) { if (edge->input()->parentNode()->IsBlock() && static_cast(edge->input()->parentNode())->type() == Block::kTransition && edge->input() == static_cast(edge->input()->parentNode())->out_block_input()) { transitions_to_move_.append(edge->input()); } } } void BlockSplitCommand::redo() { track_->BlockInvalidateCache(); block_->set_length(new_length_); static_cast(block_->parent())->AddNode(new_block_); Node::CopyInputs(block_, new_block_); // Will re-parent new_block_ to the track's graph track_->InsertBlockAfter(new_block_, block_); track_->UnblockInvalidateCache(); foreach (NodeInput* transition, transitions_to_move_) { NodeParam::DisconnectEdge(block_->output(), transition); NodeParam::ConnectEdge(new_block_->output(), transition); } } void BlockSplitCommand::undo() { track_->BlockInvalidateCache(); block_->set_length(old_length_); track_->RippleRemoveBlock(new_block_); TakeNodeFromParentGraph(new_block_, &memory_manager_); track_->UnblockInvalidateCache(); foreach (NodeInput* transition, transitions_to_move_) { NodeParam::DisconnectEdge(new_block_->output(), transition); NodeParam::ConnectEdge(block_->output(), transition); } } Block *BlockSplitCommand::new_block() { return new_block_; } TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational point, QUndoCommand *parent) : QUndoCommand(parent) { // Find Block that contains this time foreach (Block* b, track->Blocks()) { 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_); } BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector &blocks, const QList ×, QUndoCommand *parent) : QUndoCommand(parent), blocks_(blocks), times_(times) { QVector< QVector > split_blocks(times.size()); for (int i=0;i splits(blocks.size()); for (int j=0;jin() < time && b->out() > time) { TrackOutput* track = TrackOutput::TrackFromBlock(b); Q_ASSERT(track); BlockSplitCommand* split_command = new BlockSplitCommand(track, b, time, this); splits.replace(j, split_command->new_block()); } else { splits.replace(j, nullptr); } } split_blocks.replace(i, splits); } // Now that we've determined all the splits, we can relink everything for (int i=0;i& split_list, split_blocks) { Block::Link(split_list.at(i), split_list.at(j)); } } } } }