Files
oak-editor/app/widget/timelinewidget/undo/undo.cpp
T
itsmattkc 52fc4f064b timeline: rewrote ripple deleting into an undoable command that takes place
after the gaps are made and cleaned

Results in more reliable ripple delete behavior.

Also fixes bug that would cause gaps that were cleaned and then restored in an
undo to crash when selected.
2020-03-07 20:42:07 +11:00

682 lines
18 KiB
C++

/***
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 <http://www.gnu.org/licenses/>.
***/
#include "undo.h"
#include "core.h"
#include "node/graph.h"
#include "node/block/transition/transition.h"
Node* TakeNodeFromParentGraph(Node* n, QObject* new_parent = nullptr)
{
static_cast<NodeGraph*>(n->parent())->TakeNode(n, new_parent);
return n;
}
BlockResizeCommand::BlockResizeCommand(Block *block, rational new_length, QUndoCommand* parent) :
UndoCommand(parent),
block_(block),
old_length_(block->length()),
new_length_(new_length)
{
}
void BlockResizeCommand::redo_internal()
{
block_->set_length_and_media_out(new_length_);
}
void BlockResizeCommand::undo_internal()
{
block_->set_length_and_media_out(old_length_);
}
BlockResizeWithMediaInCommand::BlockResizeWithMediaInCommand(Block *block, rational new_length, QUndoCommand *parent) :
UndoCommand(parent),
block_(block),
old_length_(block->length()),
new_length_(new_length)
{
}
void BlockResizeWithMediaInCommand::redo_internal()
{
block_->set_length_and_media_in(new_length_);
}
void BlockResizeWithMediaInCommand::undo_internal()
{
block_->set_length_and_media_in(old_length_);
}
BlockSetMediaInCommand::BlockSetMediaInCommand(Block *block, rational new_media_in, QUndoCommand* parent) :
UndoCommand(parent),
block_(block),
old_media_in_(block->media_in()),
new_media_in_(new_media_in)
{
}
void BlockSetMediaInCommand::redo_internal()
{
block_->set_media_in(new_media_in_);
}
void BlockSetMediaInCommand::undo_internal()
{
block_->set_media_in(old_media_in_);
}
TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) :
UndoCommand(parent),
track_(track),
block_(block)
{
}
void TrackRippleRemoveBlockCommand::redo_internal()
{
before_ = block_->previous();
track_->RippleRemoveBlock(block_);
}
void TrackRippleRemoveBlockCommand::undo_internal()
{
if (before_) {
track_->InsertBlockAfter(block_, before_);
} else {
track_->PrependBlock(block_);
}
}
TrackInsertBlockAfterCommand::TrackInsertBlockAfterCommand(TrackOutput *track,
Block *block,
Block *before,
QUndoCommand *parent) :
UndoCommand(parent),
track_(track),
block_(block),
before_(before)
{
}
void TrackInsertBlockAfterCommand::redo_internal()
{
track_->InsertBlockAfter(block_, before_);
}
void TrackInsertBlockAfterCommand::undo_internal()
{
track_->RippleRemoveBlock(block_);
}
TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, rational in, rational out, QUndoCommand *parent) :
UndoCommand(parent),
track_(track),
in_(in),
out_(out),
splice_(false),
trim_out_(nullptr),
trim_in_(nullptr),
insert_(nullptr)
{
}
void TrackRippleRemoveAreaCommand::SetInsert(Block *insert)
{
insert_ = insert;
}
void TrackRippleRemoveAreaCommand::redo_internal()
{
// 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
trim_out_ = block;
splice_ = true;
// 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_) {
// Split the block here
trim_in_ = static_cast<Block*>(trim_out_->copy());
static_cast<NodeGraph*>(track_->parent())->AddNode(trim_in_);
Node::CopyInputs(trim_out_, trim_in_);
trim_out_old_length_ = trim_out_->length();
trim_out_->set_length_and_media_out(in_ - trim_out_->in());
trim_in_->set_length_and_media_in(trim_out_old_length_ - (out_ - trim_out_->in()));
track_->InsertBlockAfter(trim_in_, trim_out_);
} else {
// If we picked up a block to trim the in point of
if (trim_in_) {
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_) {
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_and_media_out(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_, insert_ ? out_ : RATIONAL_MAX);
}
void TrackRippleRemoveAreaCommand::undo_internal()
{
track_->BlockInvalidateCache();
// If we were given a block to insert, insert it here
if (insert_ != nullptr) {
track_->RippleRemoveBlock(insert_);
}
if (splice_) {
// trim_in_ is our copy and trim_out_ is our original
track_->RippleRemoveBlock(trim_in_);
delete TakeNodeFromParentGraph(trim_in_);
trim_out_->set_length_and_media_out(trim_out_old_length_);
} else {
// 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_and_media_out(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_);
}
}
removed_blocks_.clear();
// 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_);
}
}
track_->UnblockInvalidateCache();
track_->InvalidateCache(in_, insert_ ? out_ : RATIONAL_MAX);
}
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_internal()
{
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_and_media_out(in_ - track_->track_length());
static_cast<NodeGraph*>(track_->parent())->AddNode(gap_);
track_->AppendBlock(gap_);
}
track_->AppendBlock(insert_);
} else {
out_ = in_ + insert_->length();
// Place the Block at this point
TrackRippleRemoveAreaCommand::redo_internal();
}
}
void TrackPlaceBlockCommand::undo_internal()
{
if (append_) {
track_->RippleRemoveBlock(insert_);
if (gap_ != nullptr) {
track_->RippleRemoveBlock(gap_);
delete TakeNodeFromParentGraph(gap_);
}
} else {
TrackRippleRemoveAreaCommand::undo_internal();
}
for (;added_track_count_>0;added_track_count_--) {
timeline_->RemoveTrack();
}
}
BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational point, QUndoCommand *parent) :
UndoCommand(parent),
track_(track),
block_(block),
new_length_(point - block->in()),
old_length_(block->length()),
point_(point)
{
Q_ASSERT(point > block_->in() && point < block_->out() && block_->type() == Block::kClip);
// Ensures that this block is deleted if this action is undone
new_block_ = static_cast<Block*>(block_->copy());
new_block_->setParent(&memory_manager_);
// Determine if the block outputs to an "out" transition
foreach (NodeEdgePtr edge, block_->output()->edges()) {
if (edge->input()->parentNode()->IsBlock()
&& static_cast<Block*>(edge->input()->parentNode())->type() == Block::kTransition
&& edge->input() == static_cast<TransitionBlock*>(edge->input()->parentNode())->out_block_input()) {
transitions_to_move_.append(edge->input());
}
}
}
void BlockSplitCommand::redo_internal()
{
track_->BlockInvalidateCache();
static_cast<NodeGraph*>(block_->parent())->AddNode(new_block_);
Node::CopyInputs(block_, new_block_);
rational new_part_length = block_->length() - (point_ - block_->in());
block_->set_length_and_media_out(new_length_);
new_block_->set_length_and_media_in(new_part_length);
track_->InsertBlockAfter(new_block_, block_);
foreach (NodeInput* transition, transitions_to_move_) {
NodeParam::DisconnectEdge(block_->output(), transition);
NodeParam::ConnectEdge(new_block_->output(), transition);
}
track_->UnblockInvalidateCache();
}
void BlockSplitCommand::undo_internal()
{
track_->BlockInvalidateCache();
block_->set_length_and_media_out(old_length_);
track_->RippleRemoveBlock(new_block_);
TakeNodeFromParentGraph(new_block_, &memory_manager_);
foreach (NodeInput* transition, transitions_to_move_) {
NodeParam::DisconnectEdge(new_block_->output(), transition);
NodeParam::ConnectEdge(block_->output(), transition);
}
track_->UnblockInvalidateCache();
}
Block *BlockSplitCommand::new_block()
{
return new_block_;
}
TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational point, QUndoCommand *parent) :
UndoCommand(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) :
UndoCommand(parent),
track_(track),
old_(old),
replace_(replace)
{
}
void TrackReplaceBlockCommand::redo_internal()
{
track_->ReplaceBlock(old_, replace_);
}
void TrackReplaceBlockCommand::undo_internal()
{
track_->ReplaceBlock(replace_, old_);
}
TrackPrependBlockCommand::TrackPrependBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) :
UndoCommand(parent),
track_(track),
block_(block)
{
}
void TrackPrependBlockCommand::redo_internal()
{
track_->PrependBlock(block_);
}
void TrackPrependBlockCommand::undo_internal()
{
track_->RippleRemoveBlock(block_);
}
BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector<Block *> &blocks, const QList<rational> &times, QUndoCommand *parent) :
UndoCommand(parent),
blocks_(blocks),
times_(times)
{
QVector< QVector<Block*> > split_blocks(times.size());
for (int i=0;i<times.size();i++) {
const rational& time = times.at(i);
QVector<Block*> splits(blocks.size());
for (int j=0;j<blocks.size();j++) {
Block* b = blocks.at(j);
if (b->in() < 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<blocks_.size();i++) {
Block* a = blocks.at(i);
for (int j=0;j<blocks.size();j++) {
if (i == j) {
continue;
}
Block* b = blocks.at(j);
if (Block::AreLinked(a, b)) {
// These blocks are linked, ensure all the splits are linked too
foreach (const QVector<Block*>& split_list, split_blocks) {
Block::Link(split_list.at(i), split_list.at(j));
}
}
}
}
}
TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, QUndoCommand *parent) :
UndoCommand(parent),
track_list_(track_list),
track_index_(index)
{
}
void TrackCleanGapsCommand::redo_internal()
{
GapBlock* on_gap = nullptr;
QList<GapBlock*> consecutive_gaps;
TrackOutput* track = track_list_->TrackAt(track_index_);
foreach (Block* b, track->Blocks()) {
if (b) {
if (b->type() == Block::kGap) {
if (on_gap) {
consecutive_gaps.append(static_cast<GapBlock*>(b));
} else {
on_gap = static_cast<GapBlock*>(b);
}
} else if (on_gap) {
merged_gaps_.append({on_gap, on_gap->length(), consecutive_gaps});
// Remove each gap and add to the length of the merged
// We can block the IC signal because merging gaps won't actually change anything
track->BlockInvalidateCache();
rational new_gap_length = on_gap->length();
foreach (GapBlock* gap, consecutive_gaps) {
track->RippleRemoveBlock(gap);
static_cast<NodeGraph*>(track->parent())->TakeNode(gap, &memory_manager_);
new_gap_length += gap->length();
}
on_gap->set_length_and_media_out(new_gap_length);
track->UnblockInvalidateCache();
// Reset state
on_gap = nullptr;
consecutive_gaps.clear();
}
}
}
if (on_gap) {
// If we're here, we found at least one or several
removed_end_gaps_.append(on_gap);
removed_end_gaps_.append(consecutive_gaps);
foreach (GapBlock* gap, removed_end_gaps_) {
track->RippleRemoveBlock(gap);
static_cast<NodeGraph*>(track->parent())->TakeNode(gap, &memory_manager_);
}
}
}
void TrackCleanGapsCommand::undo_internal()
{
TrackOutput* track = track_list_->TrackAt(track_index_);
// Restored removed end gaps
foreach (GapBlock* gap, removed_end_gaps_) {
static_cast<NodeGraph*>(track->parent())->AddNode(gap);
track->AppendBlock(gap);
}
removed_end_gaps_.clear();
track->BlockInvalidateCache();
for (int i=merged_gaps_.size()-1;i>=0;i--) {
const MergedGap& merge_info = merged_gaps_.at(i);
merge_info.merged->set_length_and_media_out(merge_info.original_length);
GapBlock* last_gap_added = merge_info.merged;
foreach (GapBlock* gap, merge_info.removed) {
static_cast<NodeGraph*>(track->parent())->AddNode(gap);
track->InsertBlockAfter(gap, last_gap_added);
last_gap_added = gap;
}
}
track->UnblockInvalidateCache();
merged_gaps_.clear();
}
BlockSetSpeedCommand::BlockSetSpeedCommand(Block *block, const rational &new_speed, QUndoCommand *parent) :
UndoCommand(parent),
block_(block),
old_speed_(block->speed()),
new_speed_(new_speed)
{
}
void BlockSetSpeedCommand::redo_internal()
{
block_->set_speed(new_speed_);
}
void BlockSetSpeedCommand::undo_internal()
{
block_->set_speed(old_speed_);
}
TimelineRippleDeleteGapsAtRegions::TimelineRippleDeleteGapsAtRegions(ViewerOutput *vo, const TimeRangeList &regions, QUndoCommand *parent) :
UndoCommand(parent),
timeline_(vo),
regions_(regions)
{
}
void TimelineRippleDeleteGapsAtRegions::redo_internal()
{
foreach (const TimeRange& range, regions_) {
rational max_ripple_length = range.length();
QList<Block*> blocks_around_range;
foreach (TrackOutput* track, timeline_->Tracks()) {
// Get the block from every other track that is either at or just before our block's in point
Block* block_at_time = track->NearestBlockBeforeOrAt(range.in());
if (block_at_time) {
if (block_at_time->type() == Block::kGap) {
max_ripple_length = qMin(block_at_time->length(), max_ripple_length);
} else {
max_ripple_length = 0;
break;
}
blocks_around_range.append(block_at_time);
}
}
if (max_ripple_length > 0) {
foreach (Block* resize, blocks_around_range) {
if (resize->length() == max_ripple_length) {
// Remove block entirely
TrackRippleRemoveBlockCommand* remove_command = new TrackRippleRemoveBlockCommand(TrackOutput::TrackFromBlock(resize), resize);
remove_command->redo();
commands_.append(remove_command);
} else {
// Resize block
BlockResizeCommand* resize_command = new BlockResizeCommand(resize, resize->length() - max_ripple_length);
resize_command->redo();
commands_.append(resize_command);
}
}
}
}
}
void TimelineRippleDeleteGapsAtRegions::undo_internal()
{
for (int i=commands_.size()-1;i>=0;i--) {
commands_.at(i)->undo();
delete commands_.at(i);
}
commands_.empty();
}