diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp
index 85b92530b..09289ac8d 100644
--- a/app/node/block/block.cpp
+++ b/app/node/block/block.cpp
@@ -31,6 +31,7 @@ Block::Block() :
previous_(nullptr),
next_(nullptr),
track_(nullptr),
+ index_(-1),
in_transition_(nullptr),
out_transition_(nullptr)
{
diff --git a/app/node/block/block.h b/app/node/block/block.h
index bf597c001..53bc42a31 100644
--- a/app/node/block/block.h
+++ b/app/node/block/block.h
@@ -165,6 +165,16 @@ public:
out_transition_ = t;
}
+ int index() const
+ {
+ return index_;
+ }
+
+ void set_index(int i)
+ {
+ index_ = i;
+ }
+
virtual void Hash(QCryptographicHash &hash, const rational &time) const override;
public slots:
@@ -201,6 +211,7 @@ private:
rational in_point_;
rational out_point_;
Track* track_;
+ int index_;
TransitionBlock* in_transition_;
TransitionBlock* out_transition_;
diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp
index 5870b602a..eec21094b 100644
--- a/app/node/output/track/track.cpp
+++ b/app/node/output/track/track.cpp
@@ -286,7 +286,8 @@ void Track::InvalidateCache(const TimeRange& range, const InputConnection& from)
limited = TimeRange(qMax(range.in(), b->in()), qMin(range.out(), b->out()));
} else {
- limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), track_length()));
+ limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), last_invalidated_length_));
+ last_invalidated_length_ = track_length();
}
Node::InvalidateCache(limited, from);
@@ -294,19 +295,27 @@ void Track::InvalidateCache(const TimeRange& range, const InputConnection& from)
void Track::InsertBlockBefore(Block* block, Block* after)
{
- InsertBlockAtIndex(block, blocks_.indexOf(after));
+ if (!after) {
+ AppendBlock(block);
+ } else {
+ InsertBlockAtIndex(block, blocks_.indexOf(after));
+ }
}
void Track::InsertBlockAfter(Block *block, Block *before)
{
- int before_index = blocks_.indexOf(before);
-
- Q_ASSERT(before_index >= 0);
-
- if (before_index == blocks_.size() - 1) {
- AppendBlock(block);
+ if (!before) {
+ PrependBlock(block);
} else {
- InsertBlockAtIndex(block, before_index + 1);
+ int before_index = blocks_.indexOf(before);
+
+ Q_ASSERT(before_index >= 0);
+
+ if (before_index == blocks_.size() - 1) {
+ AppendBlock(block);
+ } else {
+ InsertBlockAtIndex(block, before_index + 1);
+ }
}
}
@@ -320,7 +329,7 @@ void Track::PrependBlock(Block *block)
EndOperation();
// Everything has shifted at this point
- Node::InvalidateCache(TimeRange(0, track_length()), InputConnection());
+ InvalidateCache(TimeRange(0, track_length()));
}
void Track::InsertBlockAtIndex(Block *block, int index)
@@ -333,7 +342,7 @@ void Track::InsertBlockAtIndex(Block *block, int index)
EndOperation();
- Node::InvalidateCache(TimeRange(block->in(), track_length()));
+ InvalidateCache(TimeRange(block->in(), track_length()));
}
void Track::AppendBlock(Block *block)
@@ -346,7 +355,7 @@ void Track::AppendBlock(Block *block)
EndOperation();
// Invalidate area that block was added to
- Node::InvalidateCache(TimeRange(block->in(), track_length()));
+ InvalidateCache(TimeRange(block->in(), track_length()));
}
void Track::RippleRemoveBlock(Block *block)
@@ -360,7 +369,7 @@ void Track::RippleRemoveBlock(Block *block)
EndOperation();
- Node::InvalidateCache(TimeRange(remove_in, qMax(track_length(), remove_out)));
+ InvalidateCache(TimeRange(remove_in, qMax(track_length(), remove_out)));
}
void Track::ReplaceBlock(Block *old, Block *replace)
@@ -376,9 +385,9 @@ void Track::ReplaceBlock(Block *old, Block *replace)
EndOperation();
if (old->length() == replace->length()) {
- Node::InvalidateCache(TimeRange(replace->in(), replace->out()));
+ InvalidateCache(TimeRange(replace->in(), replace->out()));
} else {
- Node::InvalidateCache(TimeRange(replace->in(), RATIONAL_MAX));
+ InvalidateCache(TimeRange(replace->in(), RATIONAL_MAX));
}
}
@@ -432,7 +441,7 @@ void Track::Hash(QCryptographicHash &hash, const rational &time) const
void Track::SetMuted(bool e)
{
muted_input_->SetStandardValue(e);
- Node::InvalidateCache(TimeRange(0, track_length()));
+ InvalidateCache(TimeRange(0, track_length()));
}
void Track::SetLocked(bool e)
@@ -454,6 +463,8 @@ void Track::UpdateInOutFrom(int index)
last_out += b->length();
b->set_out(last_out);
+
+ b->set_index(i);
}
emit BlocksRefreshed();
@@ -480,13 +491,16 @@ int Track::GetCacheIndexFromArrayIndex(int index) const
void Track::SetLengthInternal(const rational &r, bool invalidate)
{
if (r != track_length_) {
+ // TimeRange will automatically normalize so that the shorter number is the in and the longer
+ // is the out
TimeRange invalidate_range(track_length_, r);
track_length_ = r;
+ last_invalidated_length_ = qMax(last_invalidated_length_, track_length_);
emit TrackLengthChanged();
if (invalidate) {
- Node::InvalidateCache(invalidate_range);
+ InvalidateCache(invalidate_range);
}
}
}
@@ -557,7 +571,7 @@ void Track::BlockConnected(Node *node, int element)
connect(block, &Block::LengthChanged, this, &Track::BlockLengthChanged);
// Invalidate cache now that block should have an in point
- Node::InvalidateCache(TimeRange(block->in(), track_length()));
+ InvalidateCache(TimeRange(block->in(), track_length()));
// Emit block added signal
emit BlockAdded(block);
@@ -615,7 +629,7 @@ void Track::BlockDisconnected(Node* node, int element)
disconnect(b, &Block::LengthChanged, this, &Track::BlockLengthChanged);
- Node::InvalidateCache(invalidate_range);
+ InvalidateCache(invalidate_range);
}
void Track::BlockLengthChanged()
@@ -631,7 +645,7 @@ void Track::BlockLengthChanged()
TimeRange invalidate_region(qMin(old_out, new_out), track_length());
- Node::InvalidateCache(invalidate_region);
+ InvalidateCache(invalidate_region);
}
void Track::MutedInputValueChanged()
diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h
index c8609a9ed..7eedcd6a1 100644
--- a/app/node/output/track/track.h
+++ b/app/node/output/track/track.h
@@ -220,7 +220,7 @@ public:
return blocks_;
}
- virtual void InvalidateCache(const TimeRange& range, const InputConnection& from) override;
+ virtual void InvalidateCache(const TimeRange& range, const InputConnection& from = InputConnection()) override;
/**
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
@@ -358,6 +358,8 @@ private:
rational track_length_;
+ rational last_invalidated_length_;
+
double track_height_;
int index_;
diff --git a/app/widget/timelinewidget/CMakeLists.txt b/app/widget/timelinewidget/CMakeLists.txt
index 62e276ba9..cda37359d 100644
--- a/app/widget/timelinewidget/CMakeLists.txt
+++ b/app/widget/timelinewidget/CMakeLists.txt
@@ -22,7 +22,6 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timelinewidget/timelineandtrackview.cpp
widget/timelinewidget/timelineandtrackview.h
- widget/timelinewidget/timelineundo.cpp
widget/timelinewidget/timelineundo.h
widget/timelinewidget/timelinewidget.cpp
widget/timelinewidget/timelinewidget.h
diff --git a/app/widget/timelinewidget/timelineundo.cpp b/app/widget/timelinewidget/timelineundo.cpp
deleted file mode 100644
index ecc3888ca..000000000
--- a/app/widget/timelinewidget/timelineundo.cpp
+++ /dev/null
@@ -1,1664 +0,0 @@
-/***
-
- 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 .
-
-***/
-
-#include "timelineundo.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 {
-
-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(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 nodes_to_clone;
- nodes_to_clone.append(trim_out_);
- nodes_to_clone.append(trim_out_->GetDependencies());
- QVector duplicated = Node::CopyDependencyGraph(nodes_to_clone, splice_split_command_);
- trim_in_ = static_cast(duplicated.first());
- } else {
- trim_in_ = static_cast(trim_out_->copy());
- new NodeAddCommand(static_cast(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(static_cast(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; isetParent(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_->copy());
- new_block_->setParent(&memory_manager_);
-
- // Determine if the block outputs to an "out" transition
- TransitionBlock* transition = block_->out_transition();
- if (transition) {
- transitions_to_move_.append(transition->out_block_input());
- }
-}
-
-Project *BlockSplitCommand::GetRelevantProject() const
-{
- return static_cast(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 src_nodes;
- QVector 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; icopy();
- 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(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(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(track_->parent())->project();
-}
-
-void TrackPrependBlockCommand::redo_internal()
-{
- track_->PrependBlock(block_);
-}
-
-void TrackPrependBlockCommand::undo_internal()
-{
- track_->RippleRemoveBlock(block_);
-}
-
-BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector &blocks, const QList ×, QUndoCommand *parent) :
- UndoCommand(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) {
- BlockSplitCommand* split_command = new BlockSplitCommand(b->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));
- }
- }
- }
- }
-}
-
-Project *BlockSplitPreservingLinksCommand::GetRelevantProject() const
-{
- return static_cast(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(timeline_->parent())->project();
-}
-
-void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal()
-{
- foreach (const TimeRange& range, regions_) {
- rational max_ripple_length = range.length();
-
- QList 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(resize->track(), 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(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(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 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(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(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(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(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(previous);
-
- existing_merged_gap_ = static_cast(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(previous);
- } else if (next_is_a_gap) {
- // Extend this gap to fill space left by block
- existing_gap_ = static_cast(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(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& 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(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(static_cast(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(list_->parent())->ShiftVideoCache(out_, in_);
- } else if (list_->type() == Track::kAudio) {
- static_cast(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(list_->parent())->ShiftVideoCache(in_, out_);
- } else if (list_->type() == Track::kAudio) {
- static_cast(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; itrack_list(static_cast(i)),
- in,
- out,
- this);
- }
-}
-
-Project *TimelineRippleRemoveAreaCommand::GetRelevantProject() const
-{
- return static_cast(timeline_->parent())->project();
-}
-
-TrackListRippleToolCommand::TrackListRippleToolCommand(TrackList *track_list, const QList &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(static_cast(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 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 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(track_list_->parent())->ShiftVideoCache(old_latest_pt, new_latest_pt);
- } else if (track_list_->type() == Track::kAudio) {
- static_cast(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(static_cast(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(track_list_->parent())->ShiftVideoCache(point_, point_ + length_);
- } else if (track_list_->type() == Track::kAudio) {
- static_cast(track_list_->parent())->ShiftAudioCache(point_, point_ + length_);
- }
-
- foreach (Track* track, working_tracks_) {
- track->BeginOperation();
- }
- }
-
- QVector blocks_to_split;
- QVector 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());
- block->track()->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(track_list_->parent())->ShiftVideoCache(point_ + length_, point_);
- } else if (track_list_->type() == Track::kAudio) {
- static_cast(track_list_->parent())->ShiftAudioCache(point_ + length_, point_);
- }
-
- foreach (Track* track, working_tracks_) {
- track->BeginOperation();
- }
- }
-
- // Remove added gaps
- foreach (GapBlock* gap, gaps_added_) {
- gap->track()->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(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_);
-}
-
-}
diff --git a/app/widget/timelinewidget/timelineundo.h b/app/widget/timelinewidget/timelineundo.h
index d56d8dc51..6fc80bfa7 100644
--- a/app/widget/timelinewidget/timelineundo.h
+++ b/app/widget/timelinewidget/timelineundo.h
@@ -23,17 +23,41 @@
#include
+#include "config/config.h"
+#include "core.h"
#include "node/block/block.h"
+#include "node/block/clip/clip.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
+#include "node/graph.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "timeline/timelinepoints.h"
#include "undo/undocommand.h"
-#include "widget/timelinewidget/timelinewidgetselections.h"
+#include "widget/nodeview/nodeviewundo.h"
namespace olive {
+inline bool NodeCanBeRemoved(Node* n)
+{
+ //return n->edges().isEmpty() && n->GetExclusiveDependencies().isEmpty();
+ return n->edges().empty();
+}
+
+inline QUndoCommand* CreateRemoveCommand(Node* n)
+{
+ QUndoCommand* command = new QUndoCommand();
+ Node::RemoveNodesAndExclusiveDependencies(n, command);
+ return command;
+}
+
+inline QUndoCommand* CreateAndRunRemoveCommand(Node* n)
+{
+ QUndoCommand* command = CreateRemoveCommand(n);
+ command->redo();
+ return command;
+}
+
class BlockResizeCommand : public UndoCommand {
public:
BlockResizeCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr) :
@@ -99,22 +123,233 @@ private:
rational new_length_;
};
+/**
+ * @brief Performs a trim in the timeline that only affects the block and the block adjacent
+ *
+ * Changes the length of one block while also changing the length of the block directly adjacent
+ * to compensate so that the rest of the track is unaffected.
+ *
+ * By default, this will only affect the length of gaps. If the adjacent needs to increase its
+ * length and is not a gap, a gap will be created and inserted to fill that time. This command can
+ * be set to always trim even if the adjacent clip isn't a gap with SetTrimIsARollEdit()
+ */
class BlockTrimCommand : public UndoCommand {
public:
- BlockTrimCommand(Track *track, Block* block, rational new_length, Timeline::MovementMode mode, QUndoCommand* command = nullptr);
+ BlockTrimCommand(Track *track, Block* block, rational new_length, Timeline::MovementMode mode, QUndoCommand* command = nullptr) :
+ UndoCommand(command),
+ prepped_(false),
+ track_(track),
+ block_(block),
+ new_length_(new_length),
+ mode_(mode),
+ deleted_adjacent_command_(nullptr),
+ trim_is_a_roll_edit_(false)
+ {
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual ~BlockTrimCommand() override
+ {
+ delete deleted_adjacent_command_;
+ }
+ virtual Project* GetRelevantProject() const override
+ {
+ return track_->parent()->project();
+ }
+
+ /**
+ * @brief Set this if the trim should always affect the adjacent clip and not create a gap
+ */
void SetTrimIsARollEdit(bool e)
{
trim_is_a_roll_edit_ = e;
}
+ /**
+ * @brief Set whether adjacent blocks set to zero length should be removed from the whole graph
+ *
+ * If an adjacent block's length is set to 0, it's automatically removed from the track. By
+ * default it also gets removed from the whole graph. Set this to FALSE to disable that
+ * functionality.
+ */
+ void SetRemoveZeroLengthFromGraph(bool e)
+ {
+ remove_block_from_graph_ = e;
+ }
+
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ if (!prepped_) {
+ prep();
+ prepped_ = true;
+ }
+
+ if (doing_nothing_) {
+ return;
+ }
+
+ // Begin an operation since we'll be doing a lot
+ track_->BeginOperation();
+
+ // Determine how much time to invalidate
+ TimeRange invalidate_range;
+
+ if (mode_ == Timeline::kTrimIn) {
+ invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_);
+ block_->set_length_and_media_in(new_length_);
+ } else {
+ invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_);
+ block_->set_length_and_media_out(new_length_);
+ }
+
+ if (needs_adjacent_) {
+ if (we_created_adjacent_) {
+ // Add adjacent and insert it
+ adjacent_->setParent(track_->parent());
+
+ if (mode_ == Timeline::kTrimIn) {
+ track_->InsertBlockBefore(adjacent_, block_);
+ } else {
+ track_->InsertBlockAfter(adjacent_, block_);
+ }
+ } else if (we_removed_adjacent_) {
+ track_->RippleRemoveBlock(adjacent_);
+
+ // It no longer inputs/outputs anything, remove it
+ if (remove_block_from_graph_ && NodeCanBeRemoved(adjacent_)) {
+ if (!deleted_adjacent_command_) {
+ deleted_adjacent_command_ = CreateAndRunRemoveCommand(adjacent_);
+ } else {
+ deleted_adjacent_command_->redo();
+ }
+ }
+ } else {
+ rational adjacent_length = adjacent_->length() + trim_diff_;
+
+ 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 = block_->range();
+ }
+
+ track_->InvalidateCache(invalidate_range, track_->block_input());
+ }
+
+ virtual void undo_internal() override
+ {
+ if (doing_nothing_) {
+ return;
+ }
+
+ track_->BeginOperation();
+
+ // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
+ if (needs_adjacent_) {
+ if (we_created_adjacent_) {
+ // Adjacent is ours, just delete it
+ track_->RippleRemoveBlock(adjacent_);
+ adjacent_->setParent(&memory_manager_);
+ } else {
+ if (we_removed_adjacent_) {
+ if (deleted_adjacent_command_) {
+ // We deleted adjacent, restore it now
+ deleted_adjacent_command_->undo();
+ }
+
+ if (mode_ == Timeline::kTrimIn) {
+ track_->InsertBlockBefore(adjacent_, block_);
+ } else {
+ track_->InsertBlockAfter(adjacent_, block_);
+ }
+ } else {
+ rational adjacent_length = adjacent_->length() - trim_diff_;
+
+ if (mode_ == Timeline::kTrimIn) {
+ adjacent_->set_length_and_media_out(adjacent_length);
+ } else {
+ adjacent_->set_length_and_media_in(adjacent_length);
+ }
+ }
+ }
+ }
+
+ TimeRange invalidate_range;
+
+ if (mode_ == Timeline::kTrimIn) {
+ block_->set_length_and_media_in(old_length_);
+
+ invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_);
+ } else {
+ block_->set_length_and_media_out(old_length_);
+
+ invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_);
+ }
+
+ if (block_->type() == Block::kTransition) {
+ // Whole transition needs to be invalidated
+ invalidate_range = block_->range();
+ }
+
+ track_->EndOperation();
+
+ track_->InvalidateCache(invalidate_range, track_->block_input());
+ }
private:
+ void prep()
+ {
+ // Store old length
+ old_length_ = block_->length();
+
+ // Determine if the length isn't changing, in which case we set a flag to do nothing
+ if ((doing_nothing_ = (old_length_ == new_length_))) {
+ return;
+ }
+
+ // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
+ trim_diff_ = old_length_ - new_length_;
+
+ // Retrieve our adjacent block (or nullptr if none)
+ if (mode_ == Timeline::kTrimIn) {
+ adjacent_ = block_->previous();
+ } else {
+ adjacent_ = block_->next();
+ }
+
+ // Ignore when trimming the out with no adjacent, because the user must have trimmed the end
+ // of the last block in the track, so we don't need to do anything elses
+ needs_adjacent_ = (mode_ == Timeline::kTrimIn || adjacent_);
+
+ if (needs_adjacent_) {
+ // If we're trimming shorter, we need an adjacent, so check if we have a viable one.
+ we_created_adjacent_ = (trim_diff_ > 0 && (!adjacent_ || (adjacent_->type() != Block::kGap && !trim_is_a_roll_edit_)));
+
+ if (we_created_adjacent_) {
+ // We shortened but don't have a viable adjacent to lengthen, so we create one
+ adjacent_ = new GapBlock();
+ adjacent_->set_length_and_media_out(trim_diff_);
+ } else {
+ // Determine if we're removing the adjacent
+ rational adjacent_length = adjacent_->length() + trim_diff_;
+ we_removed_adjacent_ = adjacent_length.isNull();
+ }
+ }
+ }
+
+ bool prepped_;
+ bool doing_nothing_;
+ rational trim_diff_;
+
Track* track_;
Block* block_;
rational old_length_;
@@ -122,10 +357,13 @@ private:
Timeline::MovementMode mode_;
Block* adjacent_;
+ bool needs_adjacent_;
bool we_created_adjacent_;
- bool we_deleted_adjacent_;
+ bool we_removed_adjacent_;
+ QUndoCommand* deleted_adjacent_command_;
bool trim_is_a_roll_edit_;
+ bool remove_block_from_graph_;
QObject memory_manager_;
@@ -186,11 +424,7 @@ protected:
virtual void undo_internal() override
{
- if (before_) {
- track_->InsertBlockAfter(block_, before_);
- } else {
- track_->PrependBlock(block_);
- }
+ track_->InsertBlockAfter(block_, before_);
}
private:
@@ -204,13 +438,28 @@ private:
class TrackPrependBlockCommand : public UndoCommand {
public:
- TrackPrependBlockCommand(Track* track, Block* block, QUndoCommand* parent = nullptr);
+ TrackPrependBlockCommand(Track* track, Block* block, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ track_(track),
+ block_(block)
+ {
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual Project* GetRelevantProject() const override
+ {
+ return track_->parent()->project();
+ }
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ track_->PrependBlock(block_);
+ }
+
+ virtual void undo_internal() override
+ {
+ track_->RippleRemoveBlock(block_);
+ }
private:
Track* track_;
@@ -251,6 +500,430 @@ private:
Block* before_;
};
+class BlockLinkCommand : public UndoCommand {
+public:
+ BlockLinkCommand(Block* a, Block* b, bool link, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ a_(a),
+ b_(b),
+ link_(link)
+ {
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return a_->parent()->project();
+ }
+
+protected:
+ virtual void redo_internal() override
+ {
+ if (link_) {
+ done_ = Block::Link(a_, b_);
+ } else {
+ done_ = Block::Unlink(a_, b_);
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ if (done_) {
+ if (link_) {
+ Block::Unlink(a_, b_);
+ } else {
+ Block::Link(a_, b_);
+ }
+ }
+ }
+
+private:
+ Block* a_;
+
+ Block* b_;
+
+ bool link_;
+
+ bool done_;
+
+};
+
+class BlockUnlinkAllCommand : public UndoCommand {
+public:
+ BlockUnlinkAllCommand(Block* block, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ block_(block)
+ {
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return static_cast(block_->parent())->project();
+ }
+
+protected:
+ virtual void redo_internal() override
+ {
+ unlinked_ = block_->linked_clips();
+
+ foreach (Block* link, unlinked_) {
+ Block::Unlink(block_, link);
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ foreach (Block* link, unlinked_) {
+ Block::Link(block_, link);
+ }
+
+ unlinked_.clear();
+ }
+
+private:
+ Block* block_;
+
+ QVector unlinked_;
+
+};
+
+class BlockLinkManyCommand : public UndoCommand {
+public:
+ BlockLinkManyCommand(const QVector blocks, bool link, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ blocks_(blocks)
+ {
+ foreach (Block* a, blocks_) {
+ foreach (Block* b, blocks_) {
+ if (a != b) {
+ new BlockLinkCommand(a, b, link, this);
+ }
+ }
+ }
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return blocks_.first()->parent()->project();
+ }
+
+private:
+ QVector blocks_;
+
+};
+
+class BlockSplitCommand : public UndoCommand {
+public:
+ BlockSplitCommand(Block* block, rational point, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ block_(block),
+ point_(point),
+ reconnect_tree_command_(nullptr)
+ {
+ }
+
+ virtual ~BlockSplitCommand() override
+ {
+ delete reconnect_tree_command_;
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return block_->parent()->project();
+ }
+
+ /**
+ * @brief Access the second block created as a result. Only valid after redo().
+ */
+ Block* new_block()
+ {
+ return static_cast(added_nodes_.first());
+ }
+
+protected:
+ virtual void redo_internal() override
+ {
+ old_length_ = block_->length();
+
+ Q_ASSERT(block_->type() == Block::kClip && point_ > block_->in() && point_ < block_->out());
+
+ // Determine if we're copying the dependencies as part of this command
+ bool copy_dependencies_too = Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool();
+
+ // Copy nodes if we haven't already done it
+ if (added_nodes_.isEmpty()) {
+ if (copy_dependencies_too) {
+ src_nodes_.append(block_);
+ src_nodes_.append(block_->GetDependencies());
+
+ added_nodes_.resize(src_nodes_.size());
+ for (int i=0; icopy();
+
+ // Add copy to graph
+ copy->setParent(block_->parent());
+
+ // Copy inputs
+ Node::CopyInputs(src_nodes_[i], copy, false);
+
+ // Keep in array
+ added_nodes_[i] = copy;
+
+ }
+ } else {
+ // Just copy the block itself
+ added_nodes_.append(block_->copy());
+ Node::CopyInputs(block_, added_nodes_.first(), true);
+ }
+ }
+
+ // Add all new nodes to the graph
+ foreach (Node* n, added_nodes_) {
+ n->setParent(block_->parent());
+ }
+
+ if (copy_dependencies_too) {
+ // Create equivalent connections among our copied dependency tree
+ if (!reconnect_tree_command_) {
+ reconnect_tree_command_ = new QUndoCommand();
+ Node::CopyDependencyGraph(src_nodes_, added_nodes_, reconnect_tree_command_);
+ }
+ reconnect_tree_command_->redo();
+ }
+
+ // Determine our new lengths
+ rational new_length = point_ - block_->in();
+ rational new_part_length = block_->out() - point_;
+
+ // Begin an operation
+ Track* track = block_->track();
+ track->BeginOperation();
+
+ // Set lengths
+ block_->set_length_and_media_out(new_length);
+ new_block()->set_length_and_media_in(new_part_length);
+
+ // Insert new block
+ track->InsertBlockAfter(new_block(), block_);
+
+ // If the block had an out transition, we move it to the new block
+ moved_transition_ = nullptr;
+
+ TransitionBlock* potential_transition = dynamic_cast(new_block()->next());
+ if (potential_transition) {
+ foreach (const Node::InputConnection& conn, block_->edges()) {
+ if (conn.input->parent() == potential_transition) {
+ moved_transition_ = potential_transition->out_block_input();
+ Node::DisconnectEdge(block_, moved_transition_);
+ Node::ConnectEdge(new_block(), moved_transition_);
+ break;
+ }
+ }
+ }
+
+ track->EndOperation();
+ }
+
+ virtual void undo_internal() override
+ {
+ Track* track = block_->track();
+
+ track->BeginOperation();
+
+ if (moved_transition_) {
+ Node::DisconnectEdge(new_block(), moved_transition_);
+ Node::DisconnectEdge(block_, moved_transition_);
+ }
+
+ block_->set_length_and_media_out(old_length_);
+ track->RippleRemoveBlock(new_block());
+
+ // If we ran a reconnect command, disconnect now
+ if (reconnect_tree_command_) {
+ reconnect_tree_command_->undo();
+ }
+
+ foreach (Node* n, added_nodes_) {
+ // Remove nodes
+ n->setParent(&memory_manager_);
+ }
+
+ track->EndOperation();
+ }
+
+private:
+ Block* block_;
+
+ rational old_length_;
+ rational point_;
+
+ QObject memory_manager_;
+
+ QUndoCommand* reconnect_tree_command_;
+
+ NodeInput* moved_transition_;
+
+ QVector src_nodes_;
+ QVector added_nodes_;
+
+};
+
+class BlockSplitPreservingLinksCommand : public UndoCommand {
+public:
+ BlockSplitPreservingLinksCommand(const QVector &blocks, const QList& times, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ blocks_(blocks),
+ times_(times)
+ {
+ }
+
+ virtual ~BlockSplitPreservingLinksCommand() override
+ {
+ qDeleteAll(commands_);
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return static_cast(blocks_.first()->parent())->project();
+ }
+
+protected:
+ virtual void redo_internal() override
+ {
+ if (commands_.isEmpty()) {
+ QVector< QVector > split_blocks(times_.size());
+
+ for (int i=0;i times_.at(i-1));
+
+ QVector splits(blocks_.size());
+ commands_.resize(blocks_.size());
+
+ for (int j=0;jin() < time && b->out() > time) {
+ BlockSplitCommand* split_command = new BlockSplitCommand(b, time);
+ split_command->redo();
+ splits.replace(j, split_command->new_block());
+ commands_.replace(j, split_command);
+ } 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) {
+ BlockLinkCommand* blc = new BlockLinkCommand(split_list.at(i), split_list.at(j), true);
+ blc->redo();
+ commands_.append(blc);
+ }
+ }
+ }
+ }
+ } else {
+ for (int i=0; iredo();
+ }
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ for (int i=commands_.size()-1; i>=0; i--) {
+ commands_.at(i)->undo();
+ }
+ }
+
+private:
+ QVector blocks_;
+
+ QList times_;
+
+ QVector commands_;
+
+};
+
+class TrackSplitAtTimeCommand : public UndoCommand {
+public:
+ TrackSplitAtTimeCommand(Track* track, rational point, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ prepped_(false),
+ track_(track),
+ point_(point),
+ command_(nullptr)
+ {
+ }
+
+ virtual ~TrackSplitAtTimeCommand() override
+ {
+ delete command_;
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return track_->parent()->project();
+ }
+
+protected:
+ virtual void redo_internal() override
+ {
+ if (!prepped_) {
+ // Find Block that contains this time
+ Block* b = track_->BlockContainingTime(point_);
+
+ if (b) {
+ command_ = new BlockSplitCommand(b, point_);
+ }
+
+ prepped_ = true;
+ }
+
+ if (command_) {
+ command_->redo();
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ if (command_) {
+ command_->undo();
+ }
+ }
+
+private:
+ bool prepped_;
+
+ Track* track_;
+
+ rational point_;
+
+ QUndoCommand* command_;
+
+};
+
/**
* @brief Clears the area between in and out
*
@@ -260,55 +933,296 @@ private:
*/
class TrackRippleRemoveAreaCommand : public UndoCommand {
public:
- TrackRippleRemoveAreaCommand(Track* track, rational in, rational out, QUndoCommand* parent = nullptr);
+ TrackRippleRemoveAreaCommand(Track* track, const TimeRange& range, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ prepped_(false),
+ track_(track),
+ range_(range),
+ splice_split_command_(nullptr)
+ {
+ trim_out_.block = nullptr;
+ trim_in_.block = nullptr;
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual ~TrackRippleRemoveAreaCommand() override
+ {
+ delete splice_split_command_;
+ qDeleteAll(remove_block_commands_);
+ }
- void SetInsert(Block* insert);
+ virtual Project* GetRelevantProject() const override
+ {
+ return track_->parent()->project();
+ }
+
+ /**
+ * @brief Block to insert after if you want to insert something between this ripple
+ */
+ Block* GetInsertionIndex() const
+ {
+ return insert_before_;
+ }
+
+ Block* GetSplicedBlock() const
+ {
+ if (splice_split_command_) {
+ return splice_split_command_->new_block();
+ }
+
+ return nullptr;
+ }
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ if (!prepped_) {
+ prep();
+ prepped_ = true;
+ }
-protected:
- Project* project_;
+ track_->BeginOperation();
+
+ if (splice_split_command_) {
+ // We're just splicing
+ splice_split_command_->redo();
+
+ // Trim the in of the split
+ Block* split = splice_split_command_->new_block();
+ split->set_length_and_media_in(split->length() - (range_.out() - split->in()));
+ } else {
+ if (trim_out_.block) {
+ trim_out_.block->set_length_and_media_out(trim_out_.new_length);
+ }
+
+ if (trim_in_.block) {
+ trim_in_.block->set_length_and_media_in(trim_in_.new_length);
+ }
+
+ // Perform removals
+ if (!removals_.isEmpty()) {
+ foreach (auto op, removals_) {
+ // Ripple remove them all first
+ track_->RippleRemoveBlock(op.block);
+ }
+
+ // Create undo commands for node removals where possible
+ if (remove_block_commands_.isEmpty()) {
+ foreach (auto op, removals_) {
+ if (NodeCanBeRemoved(op.block)) {
+ remove_block_commands_.append(CreateRemoveCommand(op.block));
+ }
+ }
+ }
+
+ foreach (QUndoCommand* c, remove_block_commands_) {
+ c->redo();
+ }
+ }
+ }
+
+ track_->EndOperation();
+
+ track_->InvalidateCache(TimeRange(range_.in(), RATIONAL_MAX));
+ }
+
+ virtual void undo_internal() override
+ {
+ // Begin operations
+ track_->BeginOperation();
+
+ if (splice_split_command_) {
+ splice_split_command_->undo();
+ } else {
+ if (trim_out_.block) {
+ trim_out_.block->set_length_and_media_out(trim_out_.old_length);
+ }
+
+ if (trim_in_.block) {
+ trim_in_.block->set_length_and_media_in(trim_in_.old_length);
+ }
+
+ // Un-remove any blocks
+ for (int i=remove_block_commands_.size()-1; i>=0; i--) {
+ remove_block_commands_.at(i)->undo();
+ }
+
+ foreach (auto op, removals_) {
+ track_->InsertBlockAfter(op.block, op.before);
+ }
+ }
+
+ // End operations and invalidate
+ track_->EndOperation();
+
+ track_->InvalidateCache(TimeRange(range_.in(), RATIONAL_MAX));
+ }
+
+private:
+ bool prepped_;
+
+ void prep()
+ {
+ // Determine precisely what will be happening to these tracks
+ Block* first_block = track_->NearestBlockBeforeOrAt(range_.in());
+
+ if (!first_block) {
+ // No blocks at this time, nothing to be done on this track
+ return;
+ }
+
+ // Determine if this first block is getting trimmed or removed
+ bool first_block_is_trimmed = first_block->in() < range_.in();
+
+ insert_before_ = first_block_is_trimmed ? first_block : first_block->previous();
+
+ // If it's getting trimmed, determine if it's actually getting spliced
+ if (first_block_is_trimmed && first_block->out() > range_.out()) {
+ // This block is getting spliced, so we'll handle that later
+ splice_split_command_ = new BlockSplitCommand(first_block, range_.in());
+ } else {
+ // It's just getting trimmed or removed, so we'll append that operation
+ if (first_block_is_trimmed) {
+ trim_out_ = {first_block,
+ first_block->length(),
+ first_block->length() - (first_block->out() - range_.in())};
+ } else {
+ removals_.append(RemoveOperation({first_block, first_block->previous()}));
+ }
+
+ // Loop through the rest of the blocks and determine what to do with those
+ for (Block* next=first_block->next(); next; next=next->next()) {
+ bool trimming = (next->out() > range_.out());
+
+ if (trimming) {
+ trim_in_ = {next,
+ next->length(),
+ next->length() - (range_.out() - next->in())};
+ break;
+ } else {
+ removals_.append(RemoveOperation({next, next->previous()}));
+
+ if (next->out() == range_.out()) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ struct TrimOperation {
+ Block* block;
+ rational old_length;
+ rational new_length;
+ };
+
+ struct RemoveOperation {
+ Block* block;
+ Block* before;
+ };
Track* track_;
- rational in_;
- rational out_;
+ TimeRange range_;
- bool splice_;
- QUndoCommand* splice_split_command_;
-
- Block* trim_out_;
- Block* trim_in_;
- QVector removed_blocks_;
-
- rational trim_in_old_length_;
- rational trim_out_old_length_;
-
- rational trim_in_new_length_;
- rational trim_out_new_length_;
-
- Block* insert_;
-
- QObject memory_manager_;
+ TrimOperation trim_out_;
+ QVector removals_;
+ TrimOperation trim_in_;
+ Block* insert_before_;
+ BlockSplitCommand* splice_split_command_;
QVector remove_block_commands_;
};
class TrackListRippleRemoveAreaCommand : public UndoCommand {
public:
- TrackListRippleRemoveAreaCommand(TrackList* list, rational in, rational out, QUndoCommand* parent = nullptr);
+ TrackListRippleRemoveAreaCommand(TrackList* list, rational in, rational out, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ list_(list),
+ in_(in),
+ out_(out)
+ {
+ }
- virtual ~TrackListRippleRemoveAreaCommand() override;
+ virtual ~TrackListRippleRemoveAreaCommand() override
+ {
+ qDeleteAll(commands_);
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual Project* GetRelevantProject() const override
+ {
+ return static_cast(list_->parent())->parent()->project();
+ }
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ // Code that's only run on the first redo
+ if (commands_.isEmpty()) {
+ all_tracks_unlocked_ = true;
+
+ foreach (Track* track, list_->GetTracks()) {
+ if (track->IsLocked()) {
+ all_tracks_unlocked_ = false;
+ continue;
+ }
+
+ TrackRippleRemoveAreaCommand* c = new TrackRippleRemoveAreaCommand(track, TimeRange(in_, out_));
+ commands_.append(c);
+ working_tracks_.append(track);
+ }
+ }
+
+ 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(list_->parent())->ShiftVideoCache(out_, in_);
+ } else if (list_->type() == Track::kAudio) {
+ static_cast(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();
+ }
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ 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(list_->parent())->ShiftVideoCache(in_, out_);
+ } else if (list_->type() == Track::kAudio) {
+ static_cast(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();
+ }
+ }
+ }
private:
TrackList* list_;
@@ -327,9 +1241,22 @@ private:
class TimelineRippleRemoveAreaCommand : public UndoCommand {
public:
- TimelineRippleRemoveAreaCommand(ViewerOutput* timeline, rational in, rational out, QUndoCommand* parent = nullptr);
+ TimelineRippleRemoveAreaCommand(ViewerOutput* timeline, rational in, rational out, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ timeline_(timeline)
+ {
+ for (int i=0; itrack_list(static_cast(i)),
+ in,
+ out,
+ this);
+ }
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual Project* GetRelevantProject() const override
+ {
+ return timeline_->parent()->project();
+ }
private:
ViewerOutput* timeline_;
@@ -349,13 +1276,163 @@ public:
TrackListRippleToolCommand(TrackList* track_list,
const QList& info,
const Timeline::MovementMode& movement_mode,
- QUndoCommand* parent = nullptr);
+ QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ track_list_(track_list),
+ info_(info),
+ movement_mode_(movement_mode)
+ {
+ working_data_.resize(info_.size());
- virtual Project* GetRelevantProject() const override;
+ all_tracks_unlocked_ = (info_.size() == track_list_->GetTrackCount());
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return static_cast(track_list_->parent())->parent()->project();
+ }
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ 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 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 = working_data_[i].created_gap;
+
+ if (!gap) {
+ gap = new GapBlock();
+ working_data_[i].created_gap = gap;
+ }
+
+ gap->set_length_and_media_out(info.new_length);
+ gap->setParent(info.ref_block->parent());
+
+ 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 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(track_list_->parent())->ShiftVideoCache(old_latest_pt, new_latest_pt);
+ } else if (track_list_->type() == Track::kAudio) {
+ static_cast(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());
+ }
+ }
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ // 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_);
+ }
+ }
+ }
private:
TrackList* track_list_;
@@ -364,7 +1441,7 @@ private:
Timeline::MovementMode movement_mode_;
struct WorkingData {
- GapBlock* created_gap;
+ GapBlock* created_gap = nullptr;
Block* removed_gap_after;
};
@@ -383,75 +1460,115 @@ private:
* either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of
* the Sequence, a GapBlock is inserted to compensate.
*/
-class TrackPlaceBlockCommand : public TrackRippleRemoveAreaCommand {
+class TrackPlaceBlockCommand : public UndoCommand {
public:
- TrackPlaceBlockCommand(TrackList *timeline, int track, Block* block, rational in, QUndoCommand* parent = nullptr);
+ TrackPlaceBlockCommand(TrackList *timeline, int track, Block* block, rational in, QUndoCommand* parent = nullptr) :
+ UndoCommand(parent),
+ timeline_(timeline),
+ track_index_(track),
+ in_(in),
+ gap_(nullptr),
+ insert_(block),
+ ripple_remove_command_(nullptr)
+ {
+ }
- virtual Project* GetRelevantProject() const override;
+ virtual ~TrackPlaceBlockCommand() override
+ {
+ delete ripple_remove_command_;
+ }
+
+ virtual Project* GetRelevantProject() const override
+ {
+ return timeline_->GetParentGraph()->project();
+ }
protected:
- virtual void redo_internal() override;
- virtual void undo_internal() override;
+ virtual void redo_internal() override
+ {
+ // 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; isetParent(timeline_->GetParentGraph());
+
+ timeline_->track_input()->ArrayAppend();
+ Node::ConnectEdge(track, timeline_->track_input(), timeline_->track_input()->ArraySize() - 1);
+ }
+ }
+
+ Track* track = timeline_->GetTrackAt(track_index_);
+
+ bool 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
+ if (!gap_) {
+ gap_ = new GapBlock();
+ gap_->set_length_and_media_out(in_ - track->track_length());
+ }
+ gap_->setParent(track->parent());
+ track->AppendBlock(gap_);
+ }
+
+ track->AppendBlock(insert_);
+ } else {
+ // Place the Block at this point
+ if (!ripple_remove_command_) {
+ ripple_remove_command_ = new TrackRippleRemoveAreaCommand(track,
+ TimeRange(in_, in_ + insert_->length()));
+ }
+
+ ripple_remove_command_->redo();
+ track->InsertBlockAfter(insert_, ripple_remove_command_->GetInsertionIndex());
+ }
+ }
+
+ virtual void undo_internal() override
+ {
+ Track* t = timeline_->GetTrackAt(track_index_);
+
+ // Firstly, remove our insert
+ t->RippleRemoveBlock(insert_);
+
+ if (ripple_remove_command_) {
+ // If we ripple removed, just undo that
+ ripple_remove_command_->undo();
+ } else if (gap_) {
+ t->RippleRemoveBlock(gap_);
+ gap_->setParent(&memory_manager_);
+ }
+
+ // Remove tracks if we added them
+ 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();
+ }
+ }
private:
TrackList* timeline_;
int track_index_;
- bool append_;
+ rational in_;
GapBlock* gap_;
+ Block* insert_;
QVector