1791 lines
50 KiB
C++
1791 lines
50 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2020 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 "config/config.h"
|
|
#include "core.h"
|
|
#include "node/block/clip/clip.h"
|
|
#include "node/block/transition/transition.h"
|
|
#include "node/graph.h"
|
|
#include "widget/nodeview/nodeviewundo.h"
|
|
#include "widget/timelinewidget/timelinewidget.h"
|
|
|
|
namespace olive {
|
|
|
|
BlockResizeCommand::BlockResizeCommand(Block *block, rational new_length, QUndoCommand* parent) :
|
|
UndoCommand(parent),
|
|
block_(block),
|
|
old_length_(block->length()),
|
|
new_length_(new_length)
|
|
{
|
|
}
|
|
|
|
Project *BlockResizeCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
Project *BlockResizeWithMediaInCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
Project *BlockSetMediaInCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void BlockSetMediaInCommand::redo_internal()
|
|
{
|
|
block_->set_media_in(new_media_in_);
|
|
}
|
|
|
|
void BlockSetMediaInCommand::undo_internal()
|
|
{
|
|
block_->set_media_in(old_media_in_);
|
|
}
|
|
|
|
TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(Track *track, Block *block, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
block_(block)
|
|
{
|
|
}
|
|
|
|
Project *TrackRippleRemoveBlockCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
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(Track *track,
|
|
Block *block,
|
|
Block *before,
|
|
QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
block_(block),
|
|
before_(before)
|
|
{
|
|
}
|
|
|
|
Project *TrackInsertBlockAfterCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void TrackInsertBlockAfterCommand::redo_internal()
|
|
{
|
|
track_->InsertBlockAfter(block_, before_);
|
|
}
|
|
|
|
void TrackInsertBlockAfterCommand::undo_internal()
|
|
{
|
|
track_->RippleRemoveBlock(block_);
|
|
}
|
|
|
|
TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(Track *track, rational in, rational out, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
in_(in),
|
|
out_(out),
|
|
splice_(false),
|
|
splice_split_command_(nullptr),
|
|
trim_out_(nullptr),
|
|
trim_in_(nullptr),
|
|
insert_(nullptr)
|
|
{
|
|
}
|
|
|
|
Project *TrackRippleRemoveAreaCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
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_->BeginOperation();
|
|
|
|
// If we picked up a block to splice
|
|
if (splice_) {
|
|
|
|
if (trim_out_->type() == Block::kGap && !insert_) {
|
|
|
|
// Gaps shouldn't be split, just trim the difference
|
|
trim_out_->set_length_and_media_out(trim_out_->length() - (out_ - in_));
|
|
|
|
} else {
|
|
|
|
// Split the block here
|
|
splice_split_command_ = new QUndoCommand();
|
|
|
|
if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) {
|
|
QVector<Node*> nodes_to_clone;
|
|
nodes_to_clone.append(trim_out_);
|
|
nodes_to_clone.append(trim_out_->GetDependencies());
|
|
QVector<Node*> duplicated = Node::CopyDependencyGraph(nodes_to_clone, splice_split_command_);
|
|
trim_in_ = static_cast<Block*>(duplicated.first());
|
|
} else {
|
|
trim_in_ = static_cast<Block*>(trim_out_->copy());
|
|
new NodeAddCommand(static_cast<NodeGraph*>(track_->parent()), trim_in_, splice_split_command_);
|
|
new NodeCopyInputsCommand(trim_out_, trim_in_, true, splice_split_command_);
|
|
}
|
|
|
|
splice_split_command_->redo();
|
|
|
|
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);
|
|
|
|
QUndoCommand* remove_command = new QUndoCommand();
|
|
Node::RemoveNodesAndExclusiveDependencies(remove_block, remove_command);
|
|
remove_command->redo();
|
|
remove_block_commands_.append(remove_command);
|
|
}
|
|
|
|
// 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_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX),
|
|
track_->block_input());
|
|
}
|
|
|
|
void TrackRippleRemoveAreaCommand::undo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
// If we were given a block to insert, insert it here
|
|
if (insert_ != nullptr) {
|
|
track_->RippleRemoveBlock(insert_);
|
|
}
|
|
|
|
if (splice_) {
|
|
|
|
if (trim_out_->type() == Block::kGap && !insert_) {
|
|
|
|
// Just restore the length
|
|
trim_out_->set_length_and_media_out(trim_out_->length() + (out_ - in_));
|
|
|
|
} else {
|
|
|
|
// trim_in_ is our copy and trim_out_ is our original
|
|
track_->RippleRemoveBlock(trim_in_);
|
|
trim_out_->set_length_and_media_out(trim_out_old_length_);
|
|
|
|
splice_split_command_->undo();
|
|
|
|
}
|
|
|
|
} 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_);
|
|
}
|
|
|
|
// Restore blocks that were removed
|
|
for (int i=remove_block_commands_.size()-1;i>=0;i--) {
|
|
QUndoCommand* command = remove_block_commands_.at(i);
|
|
command->undo();
|
|
delete command;
|
|
}
|
|
remove_block_commands_.clear();
|
|
|
|
for (int i=removed_blocks_.size()-1;i>=0;i--) {
|
|
Block* remove_block = removed_blocks_.at(i);
|
|
|
|
if (trim_in_) {
|
|
track_->InsertBlockBefore(remove_block, trim_in_);
|
|
} else {
|
|
track_->AppendBlock(remove_block);
|
|
}
|
|
}
|
|
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_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX),
|
|
track_->block_input());
|
|
|
|
if (splice_split_command_) {
|
|
delete splice_split_command_;
|
|
splice_split_command_ = nullptr;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Project *TrackPlaceBlockCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(static_cast<ViewerOutput*>(timeline_->parent())->parent())->project();
|
|
}
|
|
|
|
void TrackPlaceBlockCommand::redo_internal()
|
|
{
|
|
// Determine if we need to add tracks
|
|
if (track_index_ >= timeline_->GetTracks().size()) {
|
|
if (added_tracks_.isEmpty()) {
|
|
// First redo, create tracks now
|
|
added_tracks_.resize(track_index_ - timeline_->GetTracks().size() + 1);
|
|
|
|
for (int i=0; i<added_tracks_.size(); i++) {
|
|
added_tracks_[i] = new Track();
|
|
}
|
|
}
|
|
|
|
for (int i=0; i<added_tracks_.size(); i++) {
|
|
Track* track = added_tracks_.at(i);
|
|
track->setParent(timeline_->GetParentGraph());
|
|
|
|
timeline_->track_input()->ArrayAppend();
|
|
Node::ConnectEdge(track, timeline_->track_input(), timeline_->track_input()->ArraySize() - 1);
|
|
}
|
|
}
|
|
|
|
track_ = timeline_->GetTrackAt(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());
|
|
gap_->setParent(track_->parent());
|
|
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_);
|
|
gap_->setParent(&memory_manager_);
|
|
}
|
|
} else {
|
|
TrackRippleRemoveAreaCommand::undo_internal();
|
|
}
|
|
|
|
for (int i=added_tracks_.size()-1; i>=0; i--) {
|
|
Track* track = added_tracks_.at(i);
|
|
Node::DisconnectEdge(track, timeline_->track_input(), timeline_->track_input()->ArraySize() - 1);
|
|
track->setParent(&memory_manager_);
|
|
timeline_->track_input()->ArrayRemoveLast();
|
|
}
|
|
}
|
|
|
|
BlockSplitCommand::BlockSplitCommand(Track* 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
|
|
TransitionBlock* transition = TransitionBlock::GetBlockOutTransition(block_);
|
|
if (transition) {
|
|
transitions_to_move_.append(transition->out_block_input());
|
|
}
|
|
}
|
|
|
|
Project *BlockSplitCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void BlockSplitCommand::redo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
NodeGraph* graph = block_->parent();
|
|
|
|
add_command_ = new QUndoCommand();
|
|
new NodeAddCommand(graph, new_block_, add_command_);
|
|
|
|
bool copy_dependencies_too = Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool();
|
|
|
|
new NodeCopyInputsCommand(block_, new_block_, !copy_dependencies_too, add_command_);
|
|
|
|
if (copy_dependencies_too) {
|
|
|
|
QVector<Node*> src_nodes;
|
|
QVector<Node*> dst_nodes;
|
|
|
|
src_nodes.append(block_);
|
|
src_nodes.append(block_->GetDependencies());
|
|
|
|
dst_nodes.resize(src_nodes.size());
|
|
dst_nodes[0] = new_block_;
|
|
for (int i=1; i<dst_nodes.size(); i++) {
|
|
dst_nodes[i] = src_nodes[i]->copy();
|
|
new NodeAddCommand(graph, dst_nodes[i], add_command_);
|
|
Node::CopyInputs(src_nodes[i], dst_nodes[i], false);
|
|
}
|
|
|
|
Node::CopyDependencyGraph(src_nodes, dst_nodes, add_command_);
|
|
|
|
}
|
|
|
|
add_command_->redo();
|
|
|
|
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_) {
|
|
Node::DisconnectEdge(block_, transition);
|
|
Node::ConnectEdge(new_block_, transition);
|
|
}
|
|
|
|
track_->EndOperation();
|
|
}
|
|
|
|
void BlockSplitCommand::undo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
foreach (NodeInput* transition, transitions_to_move_) {
|
|
Node::DisconnectEdge(new_block_, transition);
|
|
Node::ConnectEdge(block_, transition);
|
|
}
|
|
|
|
block_->set_length_and_media_out(old_length_);
|
|
track_->RippleRemoveBlock(new_block_);
|
|
|
|
add_command_->undo();
|
|
new_block_->setParent(&memory_manager_);
|
|
delete add_command_;
|
|
|
|
track_->EndOperation();
|
|
}
|
|
|
|
Block *BlockSplitCommand::new_block()
|
|
{
|
|
return new_block_;
|
|
}
|
|
|
|
TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(Track *track, rational point, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Project *TrackSplitAtTimeCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
TrackReplaceBlockCommand::TrackReplaceBlockCommand(Track* track, Block *old, Block *replace, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
old_(old),
|
|
replace_(replace)
|
|
{
|
|
}
|
|
|
|
Project *TrackReplaceBlockCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
void TrackReplaceBlockCommand::redo_internal()
|
|
{
|
|
track_->ReplaceBlock(old_, replace_);
|
|
}
|
|
|
|
void TrackReplaceBlockCommand::undo_internal()
|
|
{
|
|
track_->ReplaceBlock(replace_, old_);
|
|
}
|
|
|
|
TrackPrependBlockCommand::TrackPrependBlockCommand(Track *track, Block *block, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
block_(block)
|
|
{
|
|
}
|
|
|
|
Project *TrackPrependBlockCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
void TrackPrependBlockCommand::redo_internal()
|
|
{
|
|
track_->PrependBlock(block_);
|
|
}
|
|
|
|
void TrackPrependBlockCommand::undo_internal()
|
|
{
|
|
track_->RippleRemoveBlock(block_);
|
|
}
|
|
|
|
BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector<Block *> &blocks, const QList<rational> ×, 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) {
|
|
Track* track = Track::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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Project *BlockSplitPreservingLinksCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(blocks_.first()->parent())->project();
|
|
}
|
|
|
|
TimelineRippleDeleteGapsAtRegionsCommand::TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput *vo, const TimeRangeList ®ions, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
timeline_(vo),
|
|
regions_(regions)
|
|
{
|
|
}
|
|
|
|
Project *TimelineRippleDeleteGapsAtRegionsCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(timeline_->parent())->project();
|
|
}
|
|
|
|
void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal()
|
|
{
|
|
foreach (const TimeRange& range, regions_) {
|
|
rational max_ripple_length = range.length();
|
|
|
|
QList<Block*> blocks_around_range;
|
|
|
|
foreach (Track* track, timeline_->GetTracks()) {
|
|
// 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(Track::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 TimelineRippleDeleteGapsAtRegionsCommand::undo_internal()
|
|
{
|
|
for (int i=commands_.size()-1;i>=0;i--) {
|
|
commands_.at(i)->undo();
|
|
delete commands_.at(i);
|
|
}
|
|
commands_.empty();
|
|
}
|
|
|
|
WorkareaSetEnabledCommand::WorkareaSetEnabledCommand(Project* project, TimelinePoints *points, bool enabled, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
project_(project),
|
|
points_(points),
|
|
old_enabled_(points_->workarea()->enabled()),
|
|
new_enabled_(enabled)
|
|
{
|
|
}
|
|
|
|
Project *WorkareaSetEnabledCommand::GetRelevantProject() const
|
|
{
|
|
return project_;
|
|
}
|
|
|
|
void WorkareaSetEnabledCommand::redo_internal()
|
|
{
|
|
points_->workarea()->set_enabled(new_enabled_);
|
|
}
|
|
|
|
void WorkareaSetEnabledCommand::undo_internal()
|
|
{
|
|
points_->workarea()->set_enabled(old_enabled_);
|
|
}
|
|
|
|
WorkareaSetRangeCommand::WorkareaSetRangeCommand(Project* project, TimelinePoints *points, const TimeRange &range, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
project_(project),
|
|
points_(points),
|
|
old_range_(points_->workarea()->range()),
|
|
new_range_(range)
|
|
{
|
|
}
|
|
|
|
Project *WorkareaSetRangeCommand::GetRelevantProject() const
|
|
{
|
|
return project_;
|
|
}
|
|
|
|
void WorkareaSetRangeCommand::redo_internal()
|
|
{
|
|
points_->workarea()->set_range(new_range_);
|
|
}
|
|
|
|
void WorkareaSetRangeCommand::undo_internal()
|
|
{
|
|
points_->workarea()->set_range(old_range_);
|
|
}
|
|
|
|
BlockLinkCommand::BlockLinkCommand(Block *a, Block *b, bool link, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
a_(a),
|
|
b_(b),
|
|
link_(link)
|
|
{
|
|
}
|
|
|
|
Project *BlockLinkCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(a_->parent())->project();
|
|
}
|
|
|
|
void BlockLinkCommand::redo_internal()
|
|
{
|
|
if (link_) {
|
|
done_ = Block::Link(a_, b_);
|
|
} else {
|
|
done_ = Block::Unlink(a_, b_);
|
|
}
|
|
}
|
|
|
|
void BlockLinkCommand::undo_internal()
|
|
{
|
|
if (done_) {
|
|
if (link_) {
|
|
Block::Unlink(a_, b_);
|
|
} else {
|
|
Block::Link(a_, b_);
|
|
}
|
|
}
|
|
}
|
|
|
|
BlockUnlinkAllCommand::BlockUnlinkAllCommand(Block *block, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
block_(block)
|
|
{
|
|
}
|
|
|
|
Project *BlockUnlinkAllCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void BlockUnlinkAllCommand::redo_internal()
|
|
{
|
|
unlinked_ = block_->linked_clips();
|
|
|
|
foreach (Block* link, unlinked_) {
|
|
Block::Unlink(block_, link);
|
|
}
|
|
}
|
|
|
|
void BlockUnlinkAllCommand::undo_internal()
|
|
{
|
|
foreach (Block* link, unlinked_) {
|
|
Block::Link(block_, link);
|
|
}
|
|
|
|
unlinked_.clear();
|
|
}
|
|
|
|
BlockLinkManyCommand::BlockLinkManyCommand(const QVector<Block *> blocks, bool link, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
blocks_(blocks)
|
|
{
|
|
foreach (Block* a, blocks_) {
|
|
foreach (Block* b, blocks_) {
|
|
if (a != b) {
|
|
new BlockLinkCommand(a, b, link, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Project *BlockLinkManyCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(blocks_.first()->parent())->project();
|
|
}
|
|
|
|
BlockEnableDisableCommand::BlockEnableDisableCommand(Block *block, bool enabled, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
block_(block),
|
|
old_enabled_(block_->is_enabled()),
|
|
new_enabled_(enabled)
|
|
{
|
|
}
|
|
|
|
Project *BlockEnableDisableCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void BlockEnableDisableCommand::redo_internal()
|
|
{
|
|
block_->set_enabled(new_enabled_);
|
|
}
|
|
|
|
void BlockEnableDisableCommand::undo_internal()
|
|
{
|
|
block_->set_enabled(old_enabled_);
|
|
}
|
|
|
|
BlockTrimCommand::BlockTrimCommand(Track* track, Block *block, rational new_length, Timeline::MovementMode mode, QUndoCommand *command) :
|
|
UndoCommand(command),
|
|
track_(track),
|
|
block_(block),
|
|
old_length_(block->length()),
|
|
new_length_(new_length),
|
|
mode_(mode),
|
|
adjacent_(nullptr),
|
|
we_created_adjacent_(false),
|
|
we_deleted_adjacent_(false),
|
|
trim_is_a_roll_edit_(false)
|
|
{
|
|
}
|
|
|
|
Project *BlockTrimCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void BlockTrimCommand::redo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
|
|
rational trim_diff = old_length_ - new_length_;
|
|
|
|
TimeRange invalidate_range;
|
|
|
|
if (mode_ == Timeline::kTrimIn) {
|
|
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
|
|
block_->set_length_and_media_in(new_length_);
|
|
adjacent_ = block_->previous();
|
|
} else {
|
|
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
|
|
block_->set_length_and_media_out(new_length_);
|
|
adjacent_ = block_->next();
|
|
}
|
|
|
|
if (trim_diff > rational()) {
|
|
// If trimming SHORTER, we'll need to create/modify a gap
|
|
if (adjacent_ && (adjacent_->type() == Block::kGap || trim_is_a_roll_edit_)) {
|
|
|
|
// A gap (or equivalent) exists, simply increase the size of it
|
|
if (mode_ == Timeline::kTrimIn) {
|
|
adjacent_->set_length_and_media_out(adjacent_->length() + trim_diff);
|
|
} else {
|
|
adjacent_->set_length_and_media_in(adjacent_->length() + trim_diff);
|
|
}
|
|
|
|
} else {
|
|
|
|
// Don't create a gap if the trim was at the end of the sequence (which would be indicated by
|
|
// the mode being "trim out" and "block_->next()" being null.
|
|
if (mode_ == Timeline::kTrimIn || block_->next()) {
|
|
// We must create a gap
|
|
we_created_adjacent_ = true;
|
|
|
|
adjacent_ = new GapBlock();
|
|
adjacent_->set_length_and_media_out(trim_diff);
|
|
adjacent_->setParent(track_->parent());
|
|
|
|
if (mode_ == Timeline::kTrimIn) {
|
|
track_->InsertBlockBefore(adjacent_, block_);
|
|
} else {
|
|
track_->InsertBlockAfter(adjacent_, block_);
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
if (adjacent_) {
|
|
// If trimming LONGER, we'll need to trim the adjacent
|
|
// (assume if there's no adjacent, we're at the end of the timeline and do nothing)
|
|
rational adjacent_length = adjacent_->length() + trim_diff;
|
|
|
|
if (adjacent_length.isNull()) {
|
|
// Ripple remove block
|
|
track_->RippleRemoveBlock(adjacent_);
|
|
adjacent_->setParent(&memory_manager_);
|
|
we_deleted_adjacent_ = true;
|
|
} else if (mode_ == Timeline::kTrimIn) {
|
|
adjacent_->set_length_and_media_out(adjacent_length);
|
|
} else {
|
|
adjacent_->set_length_and_media_in(adjacent_length);
|
|
}
|
|
}
|
|
}
|
|
|
|
track_->EndOperation();
|
|
|
|
if (block_->type() == Block::kTransition) {
|
|
// Whole transition needs to be invalidated
|
|
invalidate_range = TimeRange(block_->in(), block_->out());
|
|
}
|
|
|
|
track_->Node::InvalidateCache(invalidate_range, track_->block_input());
|
|
}
|
|
|
|
void BlockTrimCommand::undo_internal()
|
|
{
|
|
TimeRange invalidate_range;
|
|
|
|
if (block_->type() == Block::kTransition) {
|
|
// Whole transition needs to be invalidated
|
|
invalidate_range = TimeRange(block_->in(), block_->out());
|
|
}
|
|
|
|
track_->BeginOperation();
|
|
|
|
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
|
|
rational trim_diff = old_length_ - new_length_;
|
|
|
|
if (trim_diff > rational()) {
|
|
// If trimmed SHORTER, we need to unadjust the gap
|
|
if (we_created_adjacent_) {
|
|
// If we created a gap, just remove it straight up
|
|
track_->RippleRemoveBlock(adjacent_);
|
|
adjacent_->setParent(&memory_manager_);
|
|
adjacent_ = nullptr;
|
|
we_created_adjacent_ = false;
|
|
} else if (adjacent_) {
|
|
// If we adjusted an existing gap, unadjust here
|
|
adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
|
|
}
|
|
} else {
|
|
if (adjacent_) {
|
|
// If trimmed LONGER, we adjusted an existing block
|
|
// (assume if there's no adjacent, we're at the end of the timeline and do nothing)
|
|
if (we_deleted_adjacent_) {
|
|
adjacent_->setParent(track_->parent());
|
|
|
|
if (mode_ == Timeline::kTrimIn) {
|
|
track_->InsertBlockBefore(adjacent_, block_);
|
|
} else {
|
|
track_->InsertBlockAfter(adjacent_, block_);
|
|
}
|
|
|
|
we_deleted_adjacent_ = false;
|
|
} else if (mode_ == Timeline::kTrimIn) {
|
|
adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
|
|
} else {
|
|
adjacent_->set_length_and_media_in(adjacent_->length() - trim_diff);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode_ == Timeline::kTrimIn) {
|
|
block_->set_length_and_media_in(old_length_);
|
|
|
|
if (block_->type() != Block::kTransition) {
|
|
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
|
|
}
|
|
} else {
|
|
block_->set_length_and_media_out(old_length_);
|
|
|
|
if (block_->type() != Block::kTransition) {
|
|
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
|
|
}
|
|
}
|
|
|
|
track_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(invalidate_range, track_->block_input());
|
|
}
|
|
|
|
TrackReplaceBlockWithGapCommand::TrackReplaceBlockWithGapCommand(Track *track, Block *block, QUndoCommand *command) :
|
|
UndoCommand(command),
|
|
track_(track),
|
|
block_(block),
|
|
existing_gap_(nullptr),
|
|
existing_merged_gap_(nullptr),
|
|
our_gap_(nullptr)
|
|
{
|
|
}
|
|
|
|
Project *TrackReplaceBlockWithGapCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(block_->parent())->project();
|
|
}
|
|
|
|
void TrackReplaceBlockWithGapCommand::redo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
// Invalidate the range inhabited by this block
|
|
TimeRange invalidate_range(block_->in(), block_->out());
|
|
|
|
if (block_->next()) {
|
|
// 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 = (previous && previous->type() == Block::kGap);
|
|
bool next_is_a_gap = (next && next->type() == Block::kGap);
|
|
|
|
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
|
|
our_gap_ = new GapBlock();
|
|
our_gap_->set_length_and_media_out(new_gap_length);
|
|
our_gap_->setParent(track_->parent());
|
|
track_->ReplaceBlock(block_, our_gap_);
|
|
}
|
|
|
|
} 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 (preceding && preceding->type() == Block::kGap) {
|
|
track_->RippleRemoveBlock(preceding);
|
|
preceding->setParent(&memory_manager_);
|
|
|
|
existing_merged_gap_ = static_cast<GapBlock*>(preceding);
|
|
}
|
|
|
|
// Remove block in question
|
|
track_->RippleRemoveBlock(block_);
|
|
}
|
|
|
|
track_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(invalidate_range, track_->block_input());
|
|
}
|
|
|
|
void TrackReplaceBlockWithGapCommand::undo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
if (our_gap_) {
|
|
|
|
// We made this gap, simply swap our gap back
|
|
track_->ReplaceBlock(our_gap_, block_);
|
|
our_gap_->setParent(&memory_manager_);
|
|
our_gap_ = nullptr;
|
|
|
|
} else if (existing_gap_) {
|
|
|
|
// 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;
|
|
|
|
} 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_);
|
|
|
|
}
|
|
|
|
track_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), track_->block_input());
|
|
}
|
|
|
|
TrackSlideCommand::TrackSlideCommand(Track* track, const QList<Block*>& moving_blocks, Block *in_adjacent, Block *out_adjacent, const rational& movement, QUndoCommand* parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
blocks_(moving_blocks),
|
|
movement_(movement),
|
|
we_created_in_adjacent_(false),
|
|
in_adjacent_(in_adjacent),
|
|
we_created_out_adjacent_(false),
|
|
out_adjacent_(out_adjacent)
|
|
{
|
|
Q_ASSERT(!movement_.isNull());
|
|
}
|
|
|
|
Project *TrackSlideCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
void TrackSlideCommand::redo_internal()
|
|
{
|
|
slide_internal(false);
|
|
}
|
|
|
|
void TrackSlideCommand::undo_internal()
|
|
{
|
|
slide_internal(true);
|
|
}
|
|
|
|
void TrackSlideCommand::slide_internal(bool undo)
|
|
{
|
|
// Make sure all movement blocks' old positions are invalidated
|
|
TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out());
|
|
|
|
track_->BeginOperation();
|
|
|
|
if (undo) {
|
|
|
|
// Undo code
|
|
if (we_created_in_adjacent_) {
|
|
// This is a gap we made, we can just delete it entirely
|
|
track_->RippleRemoveBlock(in_adjacent_);
|
|
in_adjacent_->setParent(&memory_manager_);
|
|
we_created_in_adjacent_ = false;
|
|
in_adjacent_ = nullptr;
|
|
} else if (in_adjacent_->parent() == &memory_manager_) {
|
|
// This is a gap we removed, we can re-insert it now
|
|
in_adjacent_->setParent(track_->parent());
|
|
track_->InsertBlockBefore(in_adjacent_, blocks_.first());
|
|
} else {
|
|
// We must have just resized this block
|
|
in_adjacent_->set_length_and_media_out(in_adjacent_->length() - movement_);
|
|
}
|
|
|
|
if (we_created_out_adjacent_) {
|
|
// This is a gap we made, we can just delete it entirely
|
|
track_->RippleRemoveBlock(out_adjacent_);
|
|
out_adjacent_->setParent(&memory_manager_);
|
|
we_created_out_adjacent_ = false;
|
|
out_adjacent_ = nullptr;
|
|
} else if (out_adjacent_) {
|
|
// We may not have created an out adjacent if this was the last clip in the track so we have
|
|
// to check here
|
|
if (out_adjacent_->parent() == &memory_manager_) {
|
|
// This is a gap we removed, we can re-insert it now
|
|
out_adjacent_->setParent(track_->parent());
|
|
track_->InsertBlockAfter(out_adjacent_, blocks_.last());
|
|
} else {
|
|
// We must have just resized this block
|
|
out_adjacent_->set_length_and_media_in(out_adjacent_->length() + movement_);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// Redo code
|
|
if (!in_adjacent_) {
|
|
// For any slide operation to have occurred at all with no in_adjacent, a gap will need to
|
|
// be created
|
|
GapBlock* gap = new GapBlock();
|
|
gap->set_length_and_media_out(movement_);
|
|
gap->setParent(track_->parent());
|
|
track_->InsertBlockBefore(gap, blocks_.first());
|
|
we_created_in_adjacent_ = true;
|
|
in_adjacent_ = gap;
|
|
} else if (-movement_ == in_adjacent_->length()) {
|
|
// Remove in adjacent entirely
|
|
track_->RippleRemoveBlock(in_adjacent_);
|
|
in_adjacent_->setParent(&memory_manager_);
|
|
} else {
|
|
// Resize in adjacent
|
|
in_adjacent_->set_length_and_media_out(in_adjacent_->length() + movement_);
|
|
}
|
|
|
|
if (!out_adjacent_) {
|
|
// For any slide operation to have occurred at all with no out_adjacent, a gap will need to
|
|
// be created UNLESS this is at the end of the track already
|
|
if (blocks_.last()->next()) {
|
|
GapBlock* gap = new GapBlock();
|
|
gap->set_length_and_media_out(-movement_);
|
|
gap->setParent(track_->parent());
|
|
track_->InsertBlockAfter(gap, blocks_.last());
|
|
we_created_out_adjacent_ = true;
|
|
out_adjacent_ = gap;
|
|
}
|
|
} else if (movement_ == out_adjacent_->length()) {
|
|
// Remove out adjacent entirely
|
|
track_->RippleRemoveBlock(out_adjacent_);
|
|
out_adjacent_->setParent(&memory_manager_);
|
|
} else {
|
|
// Resize out adjacent
|
|
out_adjacent_->set_length_and_media_in(out_adjacent_->length() - movement_);
|
|
}
|
|
|
|
}
|
|
|
|
track_->EndOperation();
|
|
|
|
// Make sure all movement blocks' new positions are invalidated
|
|
invalidate_range.set_range(qMin(invalidate_range.in(), blocks_.first()->in()),
|
|
qMax(invalidate_range.out(), blocks_.last()->out()));
|
|
|
|
track_->Node::InvalidateCache(invalidate_range, track_->block_input());
|
|
}
|
|
|
|
TrackListRippleRemoveAreaCommand::TrackListRippleRemoveAreaCommand(TrackList *list, rational in, rational out, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
list_(list),
|
|
in_(in),
|
|
out_(out)
|
|
{
|
|
all_tracks_unlocked_ = true;
|
|
|
|
foreach (Track* track, list_->GetTracks()) {
|
|
if (track->IsLocked()) {
|
|
all_tracks_unlocked_ = false;
|
|
continue;
|
|
}
|
|
|
|
TrackRippleRemoveAreaCommand* c = new TrackRippleRemoveAreaCommand(track, in, out);
|
|
commands_.append(c);
|
|
working_tracks_.append(track);
|
|
}
|
|
}
|
|
|
|
TrackListRippleRemoveAreaCommand::~TrackListRippleRemoveAreaCommand()
|
|
{
|
|
qDeleteAll(commands_);
|
|
}
|
|
|
|
Project *TrackListRippleRemoveAreaCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(static_cast<ViewerOutput*>(list_->parent())->parent())->project();
|
|
}
|
|
|
|
void TrackListRippleRemoveAreaCommand::redo_internal()
|
|
{
|
|
if (all_tracks_unlocked_) {
|
|
// We can optimize here by simply shifting the whole cache forward instead of re-caching
|
|
// everything following this time
|
|
if (list_->type() == Track::kVideo) {
|
|
static_cast<ViewerOutput*>(list_->parent())->ShiftVideoCache(out_, in_);
|
|
} else if (list_->type() == Track::kAudio) {
|
|
static_cast<ViewerOutput*>(list_->parent())->ShiftAudioCache(out_, in_);
|
|
}
|
|
|
|
foreach (Track* track, working_tracks_) {
|
|
track->BeginOperation();
|
|
}
|
|
}
|
|
|
|
foreach (TrackRippleRemoveAreaCommand* c, commands_) {
|
|
c->redo();
|
|
}
|
|
|
|
if (all_tracks_unlocked_) {
|
|
foreach (Track* track, working_tracks_) {
|
|
track->EndOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackListRippleRemoveAreaCommand::undo_internal()
|
|
{
|
|
if (all_tracks_unlocked_) {
|
|
// We can optimize here by simply shifting the whole cache forward instead of re-caching
|
|
// everything following this time
|
|
if (list_->type() == Track::kVideo) {
|
|
static_cast<ViewerOutput*>(list_->parent())->ShiftVideoCache(in_, out_);
|
|
} else if (list_->type() == Track::kAudio) {
|
|
static_cast<ViewerOutput*>(list_->parent())->ShiftAudioCache(in_, out_);
|
|
}
|
|
|
|
foreach (Track* track, working_tracks_) {
|
|
track->BeginOperation();
|
|
}
|
|
}
|
|
|
|
foreach (TrackRippleRemoveAreaCommand* c, commands_) {
|
|
c->undo();
|
|
}
|
|
|
|
if (all_tracks_unlocked_) {
|
|
foreach (Track* track, working_tracks_) {
|
|
track->EndOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
TimelineRippleRemoveAreaCommand::TimelineRippleRemoveAreaCommand(ViewerOutput *timeline, rational in, rational out, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
timeline_(timeline)
|
|
{
|
|
for (int i=0; i<Track::kCount; i++) {
|
|
new TrackListRippleRemoveAreaCommand(timeline->track_list(static_cast<Track::Type>(i)),
|
|
in,
|
|
out,
|
|
this);
|
|
}
|
|
}
|
|
|
|
Project *TimelineRippleRemoveAreaCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(timeline_->parent())->project();
|
|
}
|
|
|
|
TrackListRippleToolCommand::TrackListRippleToolCommand(TrackList *track_list, const QList<RippleInfo> &info, const Timeline::MovementMode &movement_mode, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_list_(track_list),
|
|
info_(info),
|
|
movement_mode_(movement_mode)
|
|
{
|
|
working_data_.resize(info_.size());
|
|
|
|
all_tracks_unlocked_ = (info_.size() == track_list_->GetTrackCount());
|
|
}
|
|
|
|
Project *TrackListRippleToolCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(static_cast<ViewerOutput*>(track_list_->parent())->parent())->project();
|
|
}
|
|
|
|
void TrackListRippleToolCommand::redo_internal()
|
|
{
|
|
rational old_latest_pt;
|
|
rational earliest_pt;
|
|
|
|
if (all_tracks_unlocked_) {
|
|
// We can do some optimization here
|
|
foreach (const RippleInfo& info, info_) {
|
|
info.track->BeginOperation();
|
|
}
|
|
|
|
old_latest_pt = RATIONAL_MIN;
|
|
earliest_pt = RATIONAL_MAX;
|
|
foreach (const RippleInfo& info, info_) {
|
|
if (info.block) {
|
|
old_latest_pt = qMax(old_latest_pt, info.block->out());
|
|
|
|
if (movement_mode_ == Timeline::kTrimIn) {
|
|
earliest_pt = qMin(earliest_pt, info.block->in());
|
|
} else {
|
|
earliest_pt = qMin(earliest_pt, info.block->out());
|
|
}
|
|
} else {
|
|
old_latest_pt = qMax(old_latest_pt, info.ref_block->out());
|
|
earliest_pt = qMin(earliest_pt, info.ref_block->out());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i=0;i<info_.size();i++) {
|
|
const RippleInfo& info = info_.at(i);
|
|
|
|
Block* b = info.block;
|
|
|
|
if (b) {
|
|
// This was a Block that already existed
|
|
if (info.new_length > 0) {
|
|
if (movement_mode_ == Timeline::kTrimIn) {
|
|
// We'll need to shift the media in point too
|
|
b->set_length_and_media_in(info.new_length);
|
|
} else {
|
|
b->set_length_and_media_out(info.new_length);
|
|
}
|
|
} else {
|
|
// Assume the Block was a Gap and it was reduced to zero length, remove it here
|
|
working_data_[i].removed_gap_after = b->previous();
|
|
info.track->RippleRemoveBlock(b);
|
|
b->setParent(&memory_manager_);
|
|
}
|
|
} else if (info.new_length > 0) {
|
|
// This is a gap we are creating
|
|
GapBlock* gap = new GapBlock();
|
|
gap->set_length_and_media_out(info.new_length);
|
|
gap->setParent(info.ref_block->parent());
|
|
working_data_[i].created_gap = gap;
|
|
|
|
info.track->InsertBlockAfter(gap, info.ref_block);
|
|
}
|
|
}
|
|
|
|
if (all_tracks_unlocked_) {
|
|
// We can do some optimization here
|
|
|
|
rational new_latest_pt = RATIONAL_MIN;
|
|
for (int i=0;i<info_.size();i++) {
|
|
const RippleInfo& info = info_.at(i);
|
|
|
|
if (info.block) {
|
|
if (info.new_length > 0) {
|
|
new_latest_pt = qMax(new_latest_pt, info.block->out());
|
|
} else {
|
|
new_latest_pt = qMax(new_latest_pt, info.block->in());
|
|
}
|
|
} else if (info.new_length > 0) {
|
|
new_latest_pt = qMax(new_latest_pt, working_data_.at(i).created_gap->out());
|
|
}
|
|
}
|
|
|
|
if (track_list_->type() == Track::kVideo) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftVideoCache(old_latest_pt, new_latest_pt);
|
|
} else if (track_list_->type() == Track::kAudio) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftAudioCache(old_latest_pt, new_latest_pt);
|
|
}
|
|
|
|
foreach (const RippleInfo& info, info_) {
|
|
info.track->EndOperation();
|
|
|
|
// FIXME: Untested, is this desirable behavior?
|
|
if (earliest_pt < new_latest_pt) {
|
|
info.track->InvalidateCache(TimeRange(earliest_pt, new_latest_pt),
|
|
info.track->block_input());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackListRippleToolCommand::undo_internal()
|
|
{
|
|
// FIXME: Add cache shift optimization
|
|
|
|
// Clean created gaps
|
|
for (int i=info_.size()-1; i>=0; i--) {
|
|
const RippleInfo& info = info_.at(i);
|
|
|
|
Block* b = info.block;
|
|
|
|
if (b) {
|
|
// This was a Block that already existed
|
|
if (info.new_length > 0) {
|
|
if (movement_mode_ == Timeline::kTrimIn) {
|
|
// We'll need to shift the media in point too
|
|
b->set_length_and_media_in(info.old_length);
|
|
} else {
|
|
b->set_length_and_media_out(info.old_length);
|
|
}
|
|
} else {
|
|
// Assume the Block was a Gap and it was reduced to zero length, remove it here
|
|
Block* previous_block = working_data_[i].removed_gap_after;
|
|
|
|
b->setParent(info.track->parent());
|
|
|
|
if (previous_block) {
|
|
info.track->InsertBlockAfter(b, previous_block);
|
|
} else {
|
|
info.track->PrependBlock(b);
|
|
}
|
|
}
|
|
} else if (info.new_length > 0) {
|
|
// We created a gap here, remove it
|
|
GapBlock* gap = working_data_.at(i).created_gap;
|
|
|
|
info.track->RippleRemoveBlock(gap);
|
|
gap->setParent(&memory_manager_);
|
|
}
|
|
}
|
|
}
|
|
|
|
TrackListInsertGaps::TrackListInsertGaps(TrackList *track_list, const rational &point, const rational &length, QUndoCommand *parent) :
|
|
UndoCommand(parent),
|
|
track_list_(track_list),
|
|
point_(point),
|
|
length_(length),
|
|
split_command_(nullptr)
|
|
{
|
|
all_tracks_unlocked_ = true;
|
|
|
|
foreach (Track* track, track_list_->GetTracks()) {
|
|
if (track->IsLocked()) {
|
|
all_tracks_unlocked_ = false;
|
|
continue;
|
|
}
|
|
|
|
working_tracks_.append(track);
|
|
}
|
|
}
|
|
|
|
Project *TrackListInsertGaps::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(static_cast<ViewerOutput*>(track_list_->parent())->parent())->project();
|
|
}
|
|
|
|
void TrackListInsertGaps::redo_internal()
|
|
{
|
|
if (all_tracks_unlocked_) {
|
|
// Optimize by shifting over since we have a constant amount of time being inserted
|
|
if (track_list_->type() == Track::kVideo) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftVideoCache(point_, point_ + length_);
|
|
} else if (track_list_->type() == Track::kAudio) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftAudioCache(point_, point_ + length_);
|
|
}
|
|
|
|
foreach (Track* track, working_tracks_) {
|
|
track->BeginOperation();
|
|
}
|
|
}
|
|
|
|
QVector<Block*> blocks_to_split;
|
|
QVector<Block*> blocks_to_append_gap_to;
|
|
|
|
foreach (Track* track, working_tracks_) {
|
|
foreach (Block* b, track->Blocks()) {
|
|
if (b->type() == Block::kGap && b->in() <= point_ && b->out() >= point_) {
|
|
// Found a gap at the location
|
|
gaps_to_extend_.append(b);
|
|
break;
|
|
} else if (b->type() == Block::kClip && b->out() >= point_) {
|
|
if (b->out() > point_) {
|
|
blocks_to_split.append(b);
|
|
}
|
|
|
|
blocks_to_append_gap_to.append(b);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (Block* gap, gaps_to_extend_) {
|
|
gap->set_length_and_media_out(gap->length() + length_);
|
|
}
|
|
|
|
if (!blocks_to_split.isEmpty()) {
|
|
split_command_ = new BlockSplitPreservingLinksCommand(blocks_to_split, {point_});
|
|
split_command_->redo();
|
|
}
|
|
|
|
foreach (Block* block, blocks_to_append_gap_to) {
|
|
GapBlock* gap = new GapBlock();
|
|
gap->set_length_and_media_out(length_);
|
|
gap->setParent(block->parent());
|
|
Track::TrackFromBlock(block)->InsertBlockAfter(gap, block);
|
|
gaps_added_.append(gap);
|
|
}
|
|
|
|
if (all_tracks_unlocked_) {
|
|
foreach (Track* track, working_tracks_) {
|
|
track->EndOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackListInsertGaps::undo_internal()
|
|
{
|
|
if (all_tracks_unlocked_) {
|
|
// Optimize by shifting over since we have a constant amount of time being inserted
|
|
if (track_list_->type() == Track::kVideo) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftVideoCache(point_ + length_, point_);
|
|
} else if (track_list_->type() == Track::kAudio) {
|
|
static_cast<ViewerOutput*>(track_list_->parent())->ShiftAudioCache(point_ + length_, point_);
|
|
}
|
|
|
|
foreach (Track* track, working_tracks_) {
|
|
track->BeginOperation();
|
|
}
|
|
}
|
|
|
|
// Remove added gaps
|
|
foreach (GapBlock* gap, gaps_added_) {
|
|
Track::TrackFromBlock(gap)->RippleRemoveBlock(gap);
|
|
gap->setParent(&memory_manager_);
|
|
}
|
|
gaps_added_.clear();
|
|
|
|
// Un-split blocks
|
|
if (split_command_) {
|
|
split_command_->undo();
|
|
delete split_command_;
|
|
split_command_ = nullptr;
|
|
}
|
|
|
|
// Restore original length of gaps
|
|
foreach (Block* gap, gaps_to_extend_) {
|
|
gap->set_length_and_media_out(gap->length() - length_);
|
|
}
|
|
gaps_to_extend_.clear();
|
|
|
|
if (all_tracks_unlocked_) {
|
|
foreach (Track* track, working_tracks_) {
|
|
track->EndOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
TransitionRemoveCommand::TransitionRemoveCommand(Track* track, TransitionBlock *block, QUndoCommand* parent) :
|
|
UndoCommand(parent),
|
|
track_(track),
|
|
block_(block),
|
|
out_block_(block_->connected_out_block()),
|
|
in_block_(block_->connected_in_block())
|
|
{
|
|
// Can't remove a transition in this way unless it's connected to at least one other block
|
|
Q_ASSERT(out_block_ || in_block_);
|
|
}
|
|
|
|
Project *TransitionRemoveCommand::GetRelevantProject() const
|
|
{
|
|
return static_cast<Sequence*>(track_->parent())->project();
|
|
}
|
|
|
|
void TransitionRemoveCommand::redo_internal()
|
|
{
|
|
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_, block_->in_block_input());
|
|
}
|
|
|
|
if (out_block_) {
|
|
Node::DisconnectEdge(out_block_, block_->out_block_input());
|
|
}
|
|
|
|
track_->RippleRemoveBlock(block_);
|
|
|
|
track_->EndOperation();
|
|
|
|
track_->Node::InvalidateCache(invalidate_range, track_->block_input());
|
|
}
|
|
|
|
void TransitionRemoveCommand::undo_internal()
|
|
{
|
|
track_->BeginOperation();
|
|
|
|
if (in_block_) {
|
|
track_->InsertBlockBefore(block_, in_block_);
|
|
} else {
|
|
track_->InsertBlockAfter(block_, out_block_);
|
|
}
|
|
|
|
if (in_block_) {
|
|
Node::ConnectEdge(in_block_, block_->in_block_input());
|
|
}
|
|
|
|
if (out_block_) {
|
|
Node::ConnectEdge(out_block_, block_->out_block_input());
|
|
}
|
|
|
|
// 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_->block_input());
|
|
}
|
|
|
|
TimelineSetSelectionsCommand::TimelineSetSelectionsCommand(TimelineWidget *timeline, const TimelineWidgetSelections &now, const TimelineWidgetSelections &old, QUndoCommand *parent) :
|
|
QUndoCommand(parent),
|
|
timeline_(timeline),
|
|
old_(old),
|
|
now_(now)
|
|
{
|
|
}
|
|
|
|
void TimelineSetSelectionsCommand::redo()
|
|
{
|
|
timeline_->SetSelections(now_);
|
|
}
|
|
|
|
void TimelineSetSelectionsCommand::undo()
|
|
{
|
|
timeline_->SetSelections(old_);
|
|
}
|
|
|
|
}
|