diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 797de34c4..85b92530b 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -29,7 +29,10 @@ namespace olive { Block::Block() : previous_(nullptr), - next_(nullptr) + next_(nullptr), + track_(nullptr), + in_transition_(nullptr), + out_transition_(nullptr) { length_input_ = new NodeInput(this, QStringLiteral("length_in"), NodeValue::kRational); length_input_->SetConnectable(false); @@ -59,26 +62,6 @@ QVector Block::Category() const return {kCategoryTimeline}; } -const rational &Block::in() const -{ - return in_point_; -} - -const rational &Block::out() const -{ - return out_point_; -} - -void Block::set_in(const rational &in) -{ - in_point_ = in; -} - -void Block::set_out(const rational &out) -{ - out_point_ = out; -} - rational Block::length() const { return length_input_->GetStandardValue().value(); @@ -110,31 +93,6 @@ void Block::set_length_and_media_in(const rational &length) set_length_internal(length); } -TimeRange Block::range() const -{ - return TimeRange(in(), out()); -} - -Block *Block::previous() -{ - return previous_; -} - -Block *Block::next() -{ - return next_; -} - -void Block::set_previous(Block *previous) -{ - previous_ = previous; -} - -void Block::set_next(Block *next) -{ - next_ = next; -} - rational Block::media_in() const { return media_in_input_->GetStandardValue().value(); @@ -309,16 +267,6 @@ bool Block::AreLinked(Block *a, Block *b) return a->linked_clips_.contains(b); } -const QVector &Block::linked_clips() -{ - return linked_clips_; -} - -bool Block::HasLinks() -{ - return !linked_clips_.isEmpty(); -} - void Block::Retranslate() { Node::Retranslate(); @@ -329,21 +277,6 @@ void Block::Retranslate() speed_input_->set_name(tr("Speed")); } -NodeInput *Block::length_input() const -{ - return length_input_; -} - -NodeInput *Block::media_in_input() const -{ - return media_in_input_; -} - -NodeInput *Block::speed_input() const -{ - return speed_input_; -} - void Block::Hash(QCryptographicHash &, const rational &) const { // A block does nothing by default, so we hash nothing diff --git a/app/node/block/block.h b/app/node/block/block.h index 46a315127..bf597c001 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -26,6 +26,8 @@ namespace olive { +class TransitionBlock; + /** * @brief A Node that represents a block of time, also displayable on a Timeline */ @@ -45,25 +47,68 @@ public: virtual QVector Category() const override; - const rational& in() const; - const rational& out() const; - void set_in(const rational& in); - void set_out(const rational& out); + const rational& in() const + { + return in_point_; + } + + const rational& out() const + { + return out_point_; + } + + void set_in(const rational& in) + { + in_point_ = in; + } + + void set_out(const rational& out) + { + out_point_ = out; + } rational length() const; void set_length_and_media_out(const rational &length); void set_length_and_media_in(const rational &length); - TimeRange range() const; + TimeRange range() const + { + return TimeRange(in(), out()); + } - Block* previous(); - Block* next(); - void set_previous(Block* previous); - void set_next(Block* next); + Block* previous() const + { + return previous_; + } + + Block* next() const + { + return next_; + } + + void set_previous(Block* previous) + { + previous_ = previous; + } + + void set_next(Block* next) + { + next_ = next; + } rational media_in() const; void set_media_in(const rational& media_in); + Track* track() const + { + return track_; + } + + void set_track(Track* track) + { + track_ = track; + } + bool is_enabled() const; void set_enabled(bool e); @@ -72,14 +117,53 @@ public: static bool Unlink(Block* a, Block* b); static void Unlink(const QList& blocks); static bool AreLinked(Block* a, Block* b); - const QVector& linked_clips(); - bool HasLinks(); + + const QVector& linked_clips() const + { + return linked_clips_; + } + + bool HasLinks() const + { + return !linked_clips_.isEmpty(); + } virtual void Retranslate() override; - NodeInput* length_input() const; - NodeInput* media_in_input() const; - NodeInput* speed_input() const; + NodeInput* length_input() const + { + return length_input_; + } + + NodeInput* media_in_input() const + { + return media_in_input_; + } + + NodeInput* speed_input() const + { + return speed_input_; + } + + TransitionBlock* in_transition() + { + return in_transition_; + } + + void set_in_transition(TransitionBlock* t) + { + in_transition_ = t; + } + + TransitionBlock* out_transition() + { + return out_transition_; + } + + void set_out_transition(TransitionBlock* t) + { + out_transition_ = t; + } virtual void Hash(QCryptographicHash &hash, const rational &time) const override; @@ -116,6 +200,10 @@ private: rational in_point_; rational out_point_; + Track* track_; + + TransitionBlock* in_transition_; + TransitionBlock* out_transition_; QVector linked_clips_; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 2a154ba44..05c91f364 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -172,23 +172,43 @@ void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &t void TransitionBlock::OutBlockConnected(Node *node) { // If node is not a block, this will just be null - connected_out_block_ = dynamic_cast(node); + if ((connected_out_block_ = dynamic_cast(node))) { + + Q_ASSERT(connected_out_block_->type() != Block::kTransition + && !connected_out_block_->out_transition() + && connected_out_block_ == this->previous()); + + connected_out_block_->set_out_transition(this); + } } void TransitionBlock::OutBlockDisconnected() { - connected_out_block_ = nullptr; + if (connected_out_block_) { + connected_out_block_->set_in_transition(nullptr); + connected_out_block_ = nullptr; + } } void TransitionBlock::InBlockConnected(Node *node) { // If node is not a block, this will just be null - connected_in_block_ = dynamic_cast(node); + if ((connected_in_block_ = dynamic_cast(node))) { + + Q_ASSERT(connected_in_block_->type() != Block::kTransition + && !connected_in_block_->in_transition() + && connected_in_block_ == this->next()); + + connected_in_block_->set_in_transition(this); + } } void TransitionBlock::InBlockDisconnected() { - connected_in_block_ = nullptr; + if (connected_in_block_) { + connected_in_block_->set_in_transition(nullptr); + connected_in_block_ = nullptr; + } } NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const @@ -251,33 +271,6 @@ NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const return table; } -TransitionBlock *GetBlockTransitionInternal(Block *block, Timeline::MovementMode mode) -{ - // See if this block outputs to a transition - foreach (const NodeConnectable::InputConnection& conn, block->edges()) { - TransitionBlock* transition = dynamic_cast(conn.input->parent()); - - if (transition) { - if ((mode == Timeline::kTrimIn && conn.input == transition->in_block_input()) - || (mode == Timeline::kTrimOut && conn.input == transition->out_block_input())) { - return transition; - } - } - } - - return nullptr; -} - -TransitionBlock *TransitionBlock::GetBlockInTransition(Block *block) -{ - return GetBlockTransitionInternal(block, Timeline::kTrimIn); -} - -TransitionBlock *TransitionBlock::GetBlockOutTransition(Block *block) -{ - return GetBlockTransitionInternal(block, Timeline::kTrimOut); -} - void TransitionBlock::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const { Q_UNUSED(value) diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 196824157..af1b230e3 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -52,10 +52,6 @@ public: virtual NodeValueTable Value(NodeValueDatabase &value) const override; - static TransitionBlock* GetBlockInTransition(Block* block); - - static TransitionBlock* GetBlockOutTransition(Block* block); - protected: virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const; diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index cec91c459..29f8f4dd6 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -60,12 +60,12 @@ Track::~Track() DisconnectAll(); } -void Track::set_track_type(const Type &track_type) +void Track::set_type(const Type &track_type) { track_type_ = track_type; } -const Track::Type& Track::track_type() const +const Track::Type& Track::type() const { return track_type_; } @@ -100,7 +100,7 @@ TimeRange Track::InputTimeAdjustment(NodeInput *input, int element, const TimeRa { if (input == block_input_ && element >= 0) { int cache_index = GetCacheIndexFromArrayIndex(element); - const rational& block_in = blocks_.at(cache_index).range.in(); + const rational& block_in = blocks_.at(cache_index)->in(); return input_time - block_in; } @@ -112,7 +112,7 @@ TimeRange Track::OutputTimeAdjustment(NodeInput *input, int element, const TimeR { if (input == block_input_ && element >= 0) { int cache_index = GetCacheIndexFromArrayIndex(element); - const rational& block_in = blocks_.at(cache_index).range.in(); + const rational& block_in = blocks_.at(cache_index)->in(); return input_time + block_in; } @@ -155,11 +155,6 @@ void Track::Retranslate() muted_input_->set_name(tr("Muted")); } -const int &Track::Index() -{ - return index_; -} - void Track::SetIndex(const int &index) { index_ = index; @@ -169,7 +164,7 @@ void Track::SetIndex(const int &index) Block *Track::BlockContainingTime(const rational &time) const { - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { if (block->in() < time && block->out() > time) { return block; } else if (block->out() == time) { @@ -182,7 +177,7 @@ Block *Track::BlockContainingTime(const rational &time) const Block *Track::NearestBlockBefore(const rational &time) const { - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { // Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block if (block->out() >= time) { return block; @@ -194,7 +189,7 @@ Block *Track::NearestBlockBefore(const rational &time) const Block *Track::NearestBlockBeforeOrAt(const rational &time) const { - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { // Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block if (block->out() > time) { return block; @@ -206,7 +201,7 @@ Block *Track::NearestBlockBeforeOrAt(const rational &time) const Block *Track::NearestBlockAfterOrAt(const rational &time) const { - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { // Blocks are sorted by time, so the first Block after this time is the correct Block if (block->in() >= time) { return block; @@ -218,7 +213,7 @@ Block *Track::NearestBlockAfterOrAt(const rational &time) const Block *Track::NearestBlockAfter(const rational &time) const { - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { // Blocks are sorted by time, so the first Block after this time is the correct Block if (block->in() > time) { return block; @@ -234,7 +229,7 @@ Block *Track::BlockAtTime(const rational &time) const return nullptr; } - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { if (block && block->in() <= time && block->out() > time) { @@ -257,7 +252,7 @@ QVector Track::BlocksAtTimeRange(const TimeRange &range) const return list; } - foreach (Block* block, block_cache_) { + foreach (Block* block, blocks_) { if (block && block->is_enabled() && block->out() > range.in() @@ -293,16 +288,16 @@ void Track::InvalidateCache(const TimeRange& range, const InputConnection& from) void Track::InsertBlockBefore(Block* block, Block* after) { - InsertBlockAtIndex(block, block_cache_.indexOf(after)); + InsertBlockAtIndex(block, blocks_.indexOf(after)); } void Track::InsertBlockAfter(Block *block, Block *before) { - int before_index = block_cache_.indexOf(before); + int before_index = blocks_.indexOf(before); Q_ASSERT(before_index >= 0); - if (before_index == block_cache_.size() - 1) { + if (before_index == blocks_.size() - 1) { AppendBlock(block); } else { InsertBlockAtIndex(block, before_index + 1); @@ -326,7 +321,7 @@ void Track::InsertBlockAtIndex(Block *block, int index) { BeginOperation(); - int insert_index = GetInputIndexFromCacheIndex(index); + int insert_index = GetArrayIndexFromCacheIndex(index); block_input_->ArrayInsert(insert_index); Node::ConnectEdge(block, block_input_, insert_index); @@ -355,7 +350,7 @@ void Track::RippleRemoveBlock(Block *block) rational remove_in = block->in(); rational remove_out = block->out(); - block_input_->ArrayRemove(GetInputIndexFromCacheIndex(block)); + block_input_->ArrayRemove(GetArrayIndexFromBlock(block)); EndOperation(); @@ -366,7 +361,7 @@ void Track::ReplaceBlock(Block *old, Block *replace) { BeginOperation(); - int index_of_old_block = GetInputIndexFromCacheIndex(old); + int index_of_old_block = GetArrayIndexFromBlock(old); DisconnectEdge(old, block_input_, index_of_old_block); @@ -442,11 +437,11 @@ void Track::SetLocked(bool e) void Track::UpdateInOutFrom(int index) { // Find block just before this one to find the last out point - rational last_out = (index == 0) ? 0 : block_cache_.at(index - 1)->out(); + rational last_out = (index == 0) ? 0 : blocks_.at(index - 1)->out(); // Iterate through all blocks updating their in/outs - for (int i=index; iset_in(last_out); @@ -455,24 +450,25 @@ void Track::UpdateInOutFrom(int index) b->set_out(last_out); } + emit BlocksRefreshed(); + // Update track length SetLengthInternal(last_out); } +int Track::GetArrayIndexFromBlock(Block *block) const +{ + return block_array_indexes_.at(blocks_.indexOf(block)); +} + int Track::GetArrayIndexFromCacheIndex(int index) const { - return blocks_.at(index).array_index; + return block_array_indexes_.at(index); } int Track::GetCacheIndexFromArrayIndex(int index) const { - for (int i=0; i=0; i--) { - // Find previous block - previous = dynamic_cast(block_input_->GetConnectedNode(i)); + for (int i=element+1; iArraySize(); i++) { + // Find next block because this will be the index that we want to insert at + cache_index = GetCacheIndexFromArrayIndex(i); - if (previous) { + if (cache_index >= 0) { + next = blocks_.at(cache_index); break; } } - // Find cache index - int cache_index; - if (previous) { - // Insert block just after the previous block we found - cache_index = block_cache_.indexOf(previous) + 1; + // If there was no next, this will be inserted at the end + if (cache_index == -1) { + cache_index = blocks_.size(); + } - // Use current previous' next as our next - next = previous->next(); - } else { - // Didn't find a previous, so insert block at 0 (prepend it) - cache_index = 0; - - if (!block_cache_.isEmpty()) { - next = block_cache_.first(); - } + // Determine previous block, either by using next's previous or the last block if there was no + // next. If there are neither, they'll both remain null + if (next) { + previous = next->previous(); + } else if (!blocks_.isEmpty()) { + previous = blocks_.last(); } // Insert at index - block_cache_.insert(cache_index, block); + blocks_.insert(cache_index, block); + block_array_indexes_.insert(cache_index, element); // Update previous/next if (previous) { @@ -546,6 +542,8 @@ void Track::BlockConnected(Node *node, int element) next->set_previous(block); } + block->set_track(this); + // Update ins/outs UpdateInOutFrom(cache_index); @@ -575,10 +573,16 @@ void Track::BlockDisconnected(Node* node, int element) TimeRange invalidate_range(b->in(), track_length()); - // FIXME: What happens if a user connects the same block twice? This must be addressed in the - // upcoming timeline rewrite. - block_cache_.removeOne(b); + // Get cache index + int cache_index = GetCacheIndexFromArrayIndex(element); + // Remove block here + blocks_.removeAt(cache_index); + block_array_indexes_.removeAt(cache_index); + + emit BlockRemoved(b); + + // Update previous/nexts Block* previous = b->previous(); Block* next = b->next(); @@ -592,19 +596,19 @@ void Track::BlockDisconnected(Node* node, int element) b->set_previous(nullptr); b->set_next(nullptr); + b->set_track(nullptr); + // Update lengths if (next) { - UpdateInOutFrom(block_cache_.indexOf(next)); - } else if (block_cache_.isEmpty()) { + UpdateInOutFrom(blocks_.indexOf(next)); + } else if (blocks_.isEmpty()) { SetLengthInternal(rational()); } else { - SetLengthInternal(block_cache_.last()->out()); + SetLengthInternal(blocks_.last()->out()); } disconnect(b, &Block::LengthChanged, this, &Track::BlockLengthChanged); - emit BlockRemoved(b); - Node::InvalidateCache(invalidate_range); } @@ -615,7 +619,7 @@ void Track::BlockLengthChanged() rational old_out = b->out(); - UpdateInOutFrom(block_cache_.indexOf(b)); + UpdateInOutFrom(blocks_.indexOf(b)); rational new_out = b->out(); @@ -629,4 +633,12 @@ void Track::MutedInputValueChanged() emit MutedChanged(IsMuted()); } +uint qHash(const Track::Reference &r, uint seed) +{ + // Not super efficient, but couldn't think of any better way to ensure a different hash each time + return ::qHash(QStringLiteral("%1:%2").arg(QString::number(r.type()), + QString::number(r.index())), + seed); +} + } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 132c8ea5d..e508ffe42 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -46,8 +46,8 @@ public: virtual ~Track() override; - const Track::Type& track_type() const; - void set_track_type(const Track::Type& track_type); + const Track::Type& type() const; + void set_type(const Track::Type& track_type); virtual Node* copy() const override; @@ -95,7 +95,58 @@ public: virtual void Retranslate() override; - const int& Index(); + class Reference + { + public: + Reference() : + type_(kNone), + index_(-1) + { + } + + Reference(const Track::Type& type, const int& index) : + type_(type), + index_(index) + { + } + + const Track::Type& type() const + { + return type_; + } + + const int& index() const + { + return index_; + } + + bool operator==(const Reference& ref) const + { + return type_ == ref.type_ && index_ == ref.index_; + } + + bool operator!=(const Reference& ref) const + { + return !(*this == ref); + } + + private: + Track::Type type_; + + int index_; + + }; + + Reference ToReference() const + { + return Reference(type(), Index()); + } + + const int& Index() const + { + return index_; + } + void SetIndex(const int& index); /** @@ -164,7 +215,7 @@ public: const QVector &Blocks() const { - return block_cache_; + return blocks_; } virtual void InvalidateCache(const TimeRange& range, const InputConnection& from) override; @@ -273,6 +324,11 @@ signals: */ void PreviewChanged(); + /** + * @brief Emitted when a block changes length and all the subsequent blocks had to update + */ + void BlocksRefreshed(); + protected: virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data) override; @@ -281,13 +337,16 @@ protected: private: void UpdateInOutFrom(int index); + int GetArrayIndexFromBlock(Block* block) const; + int GetArrayIndexFromCacheIndex(int index) const; int GetCacheIndexFromArrayIndex(int index) const; void SetLengthInternal(const rational& r, bool invalidate = true); - QVector block_cache_; + QVector blocks_; + QVector block_array_indexes_; NodeInput* block_input_; @@ -316,6 +375,8 @@ private slots: }; +uint qHash(const Track::Reference& r, uint seed = 0); + } #endif // TRACK_H diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index c521aae74..e19fbadee 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -36,26 +36,6 @@ TrackList::TrackList(ViewerOutput *parent, const Track::Type &type, NodeInput *t connect(track_input_, &NodeInput::InputDisconnected, this, &TrackList::TrackDisconnected); } -const Track::Type &TrackList::type() const -{ - return type_; -} - -void TrackList::TrackAddedBlock(Block *block) -{ - emit BlockAdded(block, static_cast(sender())->Index()); -} - -void TrackList::TrackRemovedBlock(Block *block) -{ - emit BlockRemoved(block); -} - -const QVector &TrackList::GetTracks() const -{ - return track_cache_; -} - Track *TrackList::GetTrackAt(int index) const { if (index < track_cache_.size()) { @@ -65,16 +45,6 @@ Track *TrackList::GetTrackAt(int index) const } } -const rational &TrackList::GetTotalLength() const -{ - return total_length_; -} - -int TrackList::GetTrackCount() const -{ - return track_cache_.size(); -} - void TrackList::TrackConnected(Node *node, int element) { if (element == -1) { @@ -114,12 +84,9 @@ void TrackList::TrackConnected(Node *node, int element) // Update track indexes in the list (including this track) UpdateTrackIndexesFrom(track_index); - connect(track, &Track::BlockAdded, this, &TrackList::TrackAddedBlock); - connect(track, &Track::BlockRemoved, this, &TrackList::TrackRemovedBlock); connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); - connect(track, &Track::TrackHeightChangedInPixels, this, &TrackList::TrackHeightChangedSlot); - track->set_track_type(type_); + track->set_type(type_); emit TrackListChanged(); @@ -150,12 +117,9 @@ void TrackList::TrackDisconnected(Node *node, int element) emit TrackRemoved(track); track->SetIndex(-1); - track->set_track_type(Track::kNone); + track->set_type(Track::kNone); - disconnect(track, &Track::BlockAdded, this, &TrackList::TrackAddedBlock); - disconnect(track, &Track::BlockRemoved, this, &TrackList::TrackRemovedBlock); disconnect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); - disconnect(track, &Track::TrackHeightChangedInPixels, this, &TrackList::TrackHeightChangedSlot); emit TrackListChanged(); @@ -187,9 +151,4 @@ void TrackList::UpdateTotalLength() emit LengthChanged(total_length_); } -void TrackList::TrackHeightChangedSlot(int height) -{ - emit TrackHeightChanged(static_cast(sender())->Index(), height); -} - } diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h index 7e2d2dc32..bf7d1e387 100644 --- a/app/node/output/track/tracklist.h +++ b/app/node/output/track/tracklist.h @@ -37,15 +37,27 @@ class TrackList : public QObject public: TrackList(ViewerOutput *parent, const Track::Type& type, NodeInput* track_input); - const Track::Type& type() const; + const Track::Type& type() const + { + return type_; + } - const QVector& GetTracks() const; + const QVector& GetTracks() const + { + return track_cache_; + } Track* GetTrackAt(int index) const; - const rational& GetTotalLength() const; + const rational& GetTotalLength() const + { + return total_length_; + } - int GetTrackCount() const; + int GetTrackCount() const + { + return track_cache_.size(); + } NodeGraph* GetParentGraph() const; @@ -55,19 +67,13 @@ public: } signals: - void BlockAdded(Block* block, int index); - - void BlockRemoved(Block* block); - - void TrackAdded(Track* track); - - void TrackRemoved(Track* track); - void TrackListChanged(); void LengthChanged(const rational &length); - void TrackHeightChanged(int index, int height); + void TrackAdded(Track* track); + + void TrackRemoved(Track* track); private: void UpdateTrackIndexesFrom(int index); @@ -94,26 +100,11 @@ private slots: */ void TrackDisconnected(Node* node, int element); - /** - * @brief Slot for when a connected Track has added a Block so we can update the UI - */ - void TrackAddedBlock(Block* block); - - /** - * @brief Slot for when a connected Track has added a Block so we can update the UI - */ - void TrackRemovedBlock(Block* block); - /** * @brief Slot for when any of the track's length changes so we can update the length of the tracklist */ void UpdateTotalLength(); - /** - * @brief Slot when a track height changes, transforms to the TrackHeightChanged signal which includes a track index - */ - void TrackHeightChangedSlot(int height); - }; } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index ff83649b3..f6d7a1a08 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -47,11 +47,8 @@ ViewerOutput::ViewerOutput() : track_lists_.replace(i, list); connect(list, &TrackList::TrackListChanged, this, &ViewerOutput::UpdateTrackCache); connect(list, &TrackList::LengthChanged, this, &ViewerOutput::VerifyLength); - connect(list, &TrackList::BlockAdded, this, &ViewerOutput::TrackListAddedBlock); - connect(list, &TrackList::BlockRemoved, this, &ViewerOutput::BlockRemoved); - connect(list, &TrackList::TrackAdded, this, &ViewerOutput::TrackListAddedTrack); + connect(list, &TrackList::TrackAdded, this, &ViewerOutput::TrackAdded); connect(list, &TrackList::TrackRemoved, this, &ViewerOutput::TrackRemoved); - connect(list, &TrackList::TrackHeightChanged, this, &ViewerOutput::TrackHeightChangedSlot); } // Create UUID for this node @@ -291,21 +288,4 @@ void ViewerOutput::EndOperation() Node::EndOperation(); } -void ViewerOutput::TrackListAddedBlock(Block *block, int index) -{ - Track::Type type = static_cast(sender())->type(); - emit BlockAdded(block, TrackReference(type, index)); -} - -void ViewerOutput::TrackListAddedTrack(Track *track) -{ - Track::Type type = static_cast(sender())->type(); - emit TrackAdded(track, type); -} - -void ViewerOutput::TrackHeightChangedSlot(int index, int height) -{ - emit TrackHeightChanged(static_cast(sender())->type(), index, height); -} - } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 4933d0f92..ed88e671a 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -32,7 +32,6 @@ #include "render/framehashcache.h" #include "render/videoparams.h" #include "timeline/timelinecommon.h" -#include "timeline/trackreference.h" namespace olive { @@ -97,7 +96,7 @@ public: return track_cache_; } - Track* GetTrackFromReference(const TrackReference& track_ref) const + Track* GetTrackFromReference(const Track::Reference& track_ref) const { return track_lists_.at(track_ref.type())->GetTrackAt(track_ref.index()); } @@ -147,14 +146,9 @@ signals: void VideoParamsChanged(); void AudioParamsChanged(); - void BlockAdded(Block* block, TrackReference track); - void BlockRemoved(Block* block); - - void TrackAdded(Track* track, Track::Type type); + void TrackAdded(Track* track); void TrackRemoved(Track* track); - void TrackHeightChanged(Track::Type type, int index, int height); - private: QUuid uuid_; @@ -185,12 +179,6 @@ private slots: void VerifyLength(); - void TrackListAddedBlock(Block* block, int index); - - void TrackListAddedTrack(Track* track); - - void TrackHeightChangedSlot(int index, int height); - }; } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 1e419cc69..ecf1f6274 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -123,12 +123,12 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const rational &in, c NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range) { // By default, just follow the in point - int active_block = track->BlockAtTime(range.in()); + Block* active_block = track->BlockAtTime(range.in()); NodeValueTable table; - if (active_block >= 0) { - table = GenerateTable(track->Blocks().at(active_block).block, range); + if (active_block) { + table = GenerateTable(active_block, range); } return table; diff --git a/app/panel/footageviewer/footageviewer.cpp b/app/panel/footageviewer/footageviewer.cpp index 6bfbb4be1..486962353 100644 --- a/app/panel/footageviewer/footageviewer.cpp +++ b/app/panel/footageviewer/footageviewer.cpp @@ -37,9 +37,9 @@ FootageViewerPanel::FootageViewerPanel(QWidget *parent) : Retranslate(); } -QList FootageViewerPanel::GetSelectedFootage() const +QVector FootageViewerPanel::GetSelectedFootage() const { - QList list; + QVector list; Footage* f = static_cast(GetTimeBasedWidget())->GetFootage(); if (f) { diff --git a/app/panel/footageviewer/footageviewer.h b/app/panel/footageviewer/footageviewer.h index d4f0d0bba..5338c1718 100644 --- a/app/panel/footageviewer/footageviewer.h +++ b/app/panel/footageviewer/footageviewer.h @@ -36,7 +36,7 @@ class FootageViewerPanel : public ViewerPanelBase, public FootageManagementPanel public: FootageViewerPanel(QWidget* parent); - virtual QList GetSelectedFootage() const override; + virtual QVector GetSelectedFootage() const override; void SetFootage(Footage* f); diff --git a/app/panel/project/footagemanagementpanel.h b/app/panel/project/footagemanagementpanel.h index e962e22ef..dba6e9be5 100644 --- a/app/panel/project/footagemanagementpanel.h +++ b/app/panel/project/footagemanagementpanel.h @@ -29,7 +29,7 @@ namespace olive { class FootageManagementPanel { public: - virtual QList GetSelectedFootage() const = 0; + virtual QVector GetSelectedFootage() const = 0; }; } diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index 5fbbd7a3c..275e6f394 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -115,7 +115,7 @@ void ProjectPanel::set_root(Item *item) Retranslate(); } -QList ProjectPanel::SelectedItems() const +QVector ProjectPanel::SelectedItems() const { return explorer_->SelectedItems(); } @@ -232,10 +232,10 @@ void ProjectPanel::SaveConnectedProject() Core::instance()->SaveProject(this->project()); } -QList ProjectPanel::GetSelectedFootage() const +QVector ProjectPanel::GetSelectedFootage() const { - QList items = SelectedItems(); - QList footage; + QVector items = SelectedItems(); + QVector footage; foreach (Item* i, items) { if (i->type() == Item::kFootage) { diff --git a/app/panel/project/project.h b/app/panel/project/project.h index c2a25e699..1da6cf335 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -44,11 +44,11 @@ public: void set_root(Item* item); - QList SelectedItems() const; + QVector SelectedItems() const; Folder* GetSelectedFolder() const; - virtual QList GetSelectedFootage() const override; + virtual QVector GetSelectedFootage() const override; ProjectViewModel* model() const; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index cdfcd13d9..82ddffe63 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -165,12 +165,12 @@ void TimelinePanel::ToggleSelectedEnabled() static_cast(GetTimeBasedWidget())->ToggleSelectedEnabled(); } -void TimelinePanel::InsertFootageAtPlayhead(const QList &footage) +void TimelinePanel::InsertFootageAtPlayhead(const QVector &footage) { static_cast(GetTimeBasedWidget())->InsertFootageAtPlayhead(footage); } -void TimelinePanel::OverwriteFootageAtPlayhead(const QList &footage) +void TimelinePanel::OverwriteFootageAtPlayhead(const QVector &footage) { static_cast(GetTimeBasedWidget())->OverwriteFootageAtPlayhead(footage); } diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 5f88651d0..5e80b222b 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -83,9 +83,9 @@ public: virtual void ToggleSelectedEnabled() override; - void InsertFootageAtPlayhead(const QList &footage); + void InsertFootageAtPlayhead(const QVector &footage); - void OverwriteFootageAtPlayhead(const QList &footage); + void OverwriteFootageAtPlayhead(const QVector &footage); protected: virtual void Retranslate() override; diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index e31f12c59..ea2f59dc6 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -297,7 +297,7 @@ void Sequence::set_default_parameters() AudioParams::kInternalFormat)); } -void Sequence::set_parameters_from_footage(const QList footage) +void Sequence::set_parameters_from_footage(const QVector footage) { bool found_video_params = false; bool found_audio_params = false; diff --git a/app/project/item/sequence/sequence.h b/app/project/item/sequence/sequence.h index 023f9378f..956e7ed7f 100644 --- a/app/project/item/sequence/sequence.h +++ b/app/project/item/sequence/sequence.h @@ -70,7 +70,7 @@ public: void set_default_parameters(); - void set_parameters_from_footage(const QList footage); + void set_parameters_from_footage(const QVector footage); ViewerOutput* viewer_output() const; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 9a1336ae0..ce3a52483 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -18,8 +18,6 @@ PreviewAutoCacher::PreviewAutoCacher() : single_frame_render_(nullptr), last_update_time_(0), ignore_next_mouse_button_(false), - video_params_changed_(false), - audio_params_changed_(false), color_manager_(nullptr) { // Set default autocache range @@ -91,7 +89,7 @@ void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cac void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) { - ClearQueue(false); + ClearVideoQueue(); // Hash these frames since that should be relatively quick. if (ignore_next_mouse_button_ || !(qApp->mouseButtons() & Qt::LeftButton)) { @@ -105,7 +103,7 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) void PreviewAutoCacher::AudioInvalidated(const TimeRange &range) { - ClearQueue(false); + ClearAudioQueue(); // Start jobs to re-render the audio at this range, split into 2 second chunks invalidated_audio_.insert(range); @@ -239,23 +237,6 @@ void PreviewAutoCacher::VideoDownloaded() delete watcher; } -void PreviewAutoCacher::VideoParamsChanged() -{ - // In case the user is pressing the mouse at this exact moment - IgnoreNextMouseButton(); - - ClearVideoQueue(); - video_params_changed_ = true; - TryRender(); -} - -void PreviewAutoCacher::AudioParamsChanged() -{ - ClearAudioQueue(); - audio_params_changed_ = true; - TryRender(); -} - void PreviewAutoCacher::SingleFrameFinished() { RenderTicketWatcher* watcher = static_cast(sender()); @@ -283,6 +264,12 @@ void PreviewAutoCacher::ProcessUpdateQueue() case QueuedJob::kValueChanged: CopyValue(job.input, job.element); break; + case QueuedJob::kVideoParamsChanged: + UpdateVideoParams(); + break; + case QueuedJob::kAudioParamsChanged: + UpdateAudioParams(); + break; } } graph_update_queue_.clear(); @@ -354,6 +341,16 @@ void PreviewAutoCacher::CopyValue(NodeInput *input, int element) NodeInput::CopyValuesOfElement(input, our_input, element); } +void PreviewAutoCacher::UpdateVideoParams() +{ + copied_viewer_node_->set_video_params(viewer_node_->video_params()); +} + +void PreviewAutoCacher::UpdateAudioParams() +{ + copied_viewer_node_->set_audio_params(viewer_node_->audio_params()); +} + void PreviewAutoCacher::SetPlayhead(const rational &playhead) { cache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value(), @@ -465,6 +462,23 @@ void PreviewAutoCacher::ValueChanged(const TimeRange &range, int element) graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, static_cast(sender()), element}); } +void PreviewAutoCacher::VideoParamsChanged() +{ + // In case the user is pressing the mouse at this exact moment + IgnoreNextMouseButton(); + + graph_update_queue_.append({QueuedJob::kVideoParamsChanged, nullptr, nullptr, -1}); + ClearVideoQueue(); + TryRender(); +} + +void PreviewAutoCacher::AudioParamsChanged() +{ + graph_update_queue_.append({QueuedJob::kAudioParamsChanged, nullptr, nullptr, -1}); + ClearAudioQueue(); + TryRender(); +} + void PreviewAutoCacher::TryRender() { if (!graph_update_queue_.isEmpty()) { @@ -475,16 +489,6 @@ void PreviewAutoCacher::TryRender() // No jobs are active, we can process the update queue ProcessUpdateQueue(); - - if (video_params_changed_) { - copied_viewer_node_->set_video_params(viewer_node_->video_params()); - video_params_changed_ = false; - } - - if (audio_params_changed_) { - copied_viewer_node_->set_audio_params(viewer_node_->audio_params()); - audio_params_changed_ = false; - } } // If we're here, we must be able to render @@ -575,7 +579,8 @@ void PreviewAutoCacher::RequeueFrames() video_tasks_.insert(watcher, hash); watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_, color_manager_, - t, RenderMode::kOffline, + t, + RenderMode::kOffline, viewer_node_->video_frame_cache(), false)); } @@ -637,14 +642,11 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) copied_viewer_node_ = nullptr; graph_update_queue_.clear(); - video_params_changed_ = false; - audio_params_changed_ = false; - // Disconnect signals for future node additions/deletions NodeGraph* graph = viewer_node_->parent(); - connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded); - connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved); + disconnect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded); + disconnect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved); // Disconnect signal (will be a no-op if the signal was never connected) disconnect(viewer_node_, @@ -695,6 +697,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) } } + last_update_time_ = QDateTime::currentMSecsSinceEpoch(); + // Connect signals for future node additions/deletions connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded); connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index e631a350c..3a6a5b429 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -90,20 +90,6 @@ public: color_manager_ = manager; } -public slots: - /** - * @brief Main handler for when the NodeGraph changes - */ - void NodeAdded(Node* node); - - void NodeRemoved(Node* node); - - void EdgeAdded(Node* output, int element); - - void EdgeRemoved(Node* output, int element); - - void ValueChanged(const TimeRange& range, int element); - private: static void GenerateHashes(ViewerOutput* viewer, FrameHashCache *cache, const QVector& times, qint64 job_time); @@ -124,6 +110,8 @@ private: void AddEdge(Node* output, NodeInput* input, int element); void RemoveEdge(Node* output, NodeInput* input, int element); void CopyValue(NodeInput* input, int element); + void UpdateVideoParams(); + void UpdateAudioParams(); class QueuedJob { public: @@ -132,7 +120,9 @@ private: kNodeRemoved, kEdgeAdded, kEdgeRemoved, - kValueChanged + kValueChanged, + kVideoParamsChanged, + kAudioParamsChanged }; Type type; @@ -172,10 +162,6 @@ private: bool ignore_next_mouse_button_; - bool video_params_changed_; - - bool audio_params_changed_; - ColorManager* color_manager_; QTimer delayed_requeue_timer_; @@ -211,6 +197,16 @@ private slots: */ void VideoDownloaded(); + void NodeAdded(Node* node); + + void NodeRemoved(Node* node); + + void EdgeAdded(Node* output, int element); + + void EdgeRemoved(Node* output, int element); + + void ValueChanged(const TimeRange& range, int element); + void VideoParamsChanged(); void AudioParamsChanged(); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 77c1db470..2557d47fb 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -188,7 +188,7 @@ void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, Stil NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const TimeRange &range) { - if (track->track_type() == Track::kAudio) { + if (track->type() == Track::kAudio) { const AudioParams& audio_params = ticket_->property("aparam").value(); diff --git a/app/timeline/CMakeLists.txt b/app/timeline/CMakeLists.txt index 07015f985..838a9ee4b 100644 --- a/app/timeline/CMakeLists.txt +++ b/app/timeline/CMakeLists.txt @@ -25,7 +25,5 @@ set(OLIVE_SOURCES timeline/timelinepoints.cpp timeline/timelineworkarea.h timeline/timelineworkarea.cpp - timeline/trackreference.h - timeline/trackreference.cpp PARENT_SCOPE ) diff --git a/app/timeline/timelinecoordinate.cpp b/app/timeline/timelinecoordinate.cpp index 2bd489ca9..c766ee484 100644 --- a/app/timeline/timelinecoordinate.cpp +++ b/app/timeline/timelinecoordinate.cpp @@ -27,7 +27,7 @@ TimelineCoordinate::TimelineCoordinate() : { } -TimelineCoordinate::TimelineCoordinate(const rational &frame, const TrackReference &track) : +TimelineCoordinate::TimelineCoordinate(const rational &frame, const Track::Reference &track) : frame_(frame), track_(track) { @@ -44,7 +44,7 @@ const rational &TimelineCoordinate::GetFrame() const return frame_; } -const TrackReference &TimelineCoordinate::GetTrack() const +const Track::Reference &TimelineCoordinate::GetTrack() const { return track_; } @@ -54,7 +54,7 @@ void TimelineCoordinate::SetFrame(const rational &frame) frame_ = frame; } -void TimelineCoordinate::SetTrack(const TrackReference &track) +void TimelineCoordinate::SetTrack(const Track::Reference &track) { track_ = track; } diff --git a/app/timeline/timelinecoordinate.h b/app/timeline/timelinecoordinate.h index a4a541e9b..725ab2764 100644 --- a/app/timeline/timelinecoordinate.h +++ b/app/timeline/timelinecoordinate.h @@ -22,7 +22,7 @@ #define TIMELINECOORDINATE_H #include "common/rational.h" -#include "trackreference.h" +#include "node/output/track/track.h" namespace olive { @@ -30,19 +30,19 @@ class TimelineCoordinate { public: TimelineCoordinate(); - TimelineCoordinate(const rational& frame, const TrackReference& track); + TimelineCoordinate(const rational& frame, const Track::Reference& track); TimelineCoordinate(const rational& frame, const Track::Type& track_type, const int& track_index); const rational& GetFrame() const; - const TrackReference& GetTrack() const; + const Track::Reference& GetTrack() const; void SetFrame(const rational& frame); - void SetTrack(const TrackReference& track); + void SetTrack(const Track::Reference& track); private: rational frame_; - TrackReference track_; + Track::Reference track_; }; diff --git a/app/timeline/trackreference.cpp b/app/timeline/trackreference.cpp deleted file mode 100644 index e2febcf43..000000000 --- a/app/timeline/trackreference.cpp +++ /dev/null @@ -1,65 +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 "trackreference.h" - -namespace olive { - -TrackReference::TrackReference() : - type_(Track::kNone), - index_(0) -{ -} - -TrackReference::TrackReference(const Track::Type &type, const int &index) : - type_(type), - index_(index) -{ -} - -const Track::Type &TrackReference::type() const -{ - return type_; -} - -const int &TrackReference::index() const -{ - return index_; -} - -bool TrackReference::operator==(const TrackReference &ref) const -{ - return type_ == ref.type_ && index_ == ref.index_; -} - -bool TrackReference::operator!=(const TrackReference &ref) const -{ - return !(*this == ref); -} - -uint qHash(const TrackReference &r, uint seed) -{ - // Not super efficient, but couldn't think of any better way to ensure a different hash each time - return ::qHash(QStringLiteral("%1:%2").arg(QString::number(r.type()), - QString::number(r.index())), - seed); -} - -} diff --git a/app/timeline/trackreference.h b/app/timeline/trackreference.h deleted file mode 100644 index d32cd36ca..000000000 --- a/app/timeline/trackreference.h +++ /dev/null @@ -1,57 +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 . - -***/ - -#ifndef TRACKREFERENCE_H -#define TRACKREFERENCE_H - -#include "node/output/track/track.h" -#include "timeline/timelinecommon.h" - -namespace olive { - -class TrackReference -{ -public: - TrackReference(); - - TrackReference(const Track::Type& type, const int& index); - - const Track::Type& type() const; - - const int& index() const; - - bool operator<(const TrackReference& ref) const; - - bool operator==(const TrackReference& ref) const; - - bool operator!=(const TrackReference& ref) const; - -private: - Track::Type type_; - - int index_; - -}; - -uint qHash(const TrackReference& r, uint seed = 0); - -} - -#endif // TRACKREFERENCE_H diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 1a0533234..1b39e4a04 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -460,13 +460,13 @@ void ProjectExplorer::set_root(Item *item) tree_view_->setRootIndex(index); } -QList ProjectExplorer::SelectedItems() const +QVector ProjectExplorer::SelectedItems() const { // Determine which view is active and get its selected indexes QModelIndexList index_list = CurrentView()->selectionModel()->selectedRows(); // Convert indexes to item objects - QList selected_items; + QVector selected_items; for (int i=0;i selected_items = SelectedItems(); + QVector selected_items = SelectedItems(); // Heuristic for finding the selected folder: // @@ -565,7 +565,7 @@ QVector ProjectExplorer::GetMediaNodesUsingFootage(Footage *item) void ProjectExplorer::DeleteSelected() { - QList selected = SelectedItems(); + QVector selected = SelectedItems(); if (selected.isEmpty()) { return; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index b2f8d6ab3..aeb9a3867 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -59,7 +59,7 @@ public: void set_root(Item* item); - QList SelectedItems() const; + QVector SelectedItems() const; /** * @brief Use a heuristic to determine which (if any) folder is selected @@ -157,7 +157,7 @@ private: QTimer rename_timer_; - QList context_menu_items_; + QVector context_menu_items_; private slots: void ItemClickedSlot(const QModelIndex& index); diff --git a/app/widget/snapservice/snapservice.h b/app/widget/snapservice/snapservice.h index c9c6a601c..ad83fb3c9 100644 --- a/app/widget/snapservice/snapservice.h +++ b/app/widget/snapservice/snapservice.h @@ -20,7 +20,7 @@ public: /** * @brief Snaps point `start_point` that is moving by `movement` to currently existing clips */ - virtual bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll) = 0; + virtual bool SnapPoint(QVector start_times, rational *movement, int snap_points = kSnapAll) = 0; virtual void HideSnaps() = 0; diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index 54ad04826..1e9b44da9 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -70,7 +70,7 @@ void TimeBasedView::TimebaseChangedEvent(const rational &) viewport()->update(); } -void TimeBasedView::EnableSnap(const QList &points) +void TimeBasedView::EnableSnap(const QVector &points) { snapped_ = true; snap_time_ = points; diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index f6549afa2..695d3d24e 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -38,7 +38,7 @@ public: static const double kMaximumScale; - void EnableSnap(const QList& points); + void EnableSnap(const QVector &points); void DisableSnap(); bool IsSnapped() const { @@ -106,7 +106,7 @@ private: QGraphicsScene scene_; bool snapped_; - QList snap_time_; + QVector snap_time_; rational end_time_; diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 9bfbcc647..17836f59a 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -28,6 +28,7 @@ #include "config/config.h" #include "core.h" #include "project/item/sequence/sequence.h" +#include "widget/timelinewidget/timelineundo.h" namespace olive { diff --git a/app/widget/timelinewidget/CMakeLists.txt b/app/widget/timelinewidget/CMakeLists.txt index 4028f0f78..62e276ba9 100644 --- a/app/widget/timelinewidget/CMakeLists.txt +++ b/app/widget/timelinewidget/CMakeLists.txt @@ -20,12 +20,13 @@ add_subdirectory(view) set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/timelinewidget/timelineandtrackview.h widget/timelinewidget/timelineandtrackview.cpp + widget/timelinewidget/timelineandtrackview.h + widget/timelinewidget/timelineundo.cpp widget/timelinewidget/timelineundo.h - widget/timelinewidget/timelinewidget.h widget/timelinewidget/timelinewidget.cpp - widget/timelinewidget/timelinewidgetselections.h + widget/timelinewidget/timelinewidget.h widget/timelinewidget/timelinewidgetselections.cpp + widget/timelinewidget/timelinewidgetselections.h PARENT_SCOPE ) diff --git a/app/widget/timelinewidget/timelineundo.cpp b/app/widget/timelinewidget/timelineundo.cpp new file mode 100644 index 000000000..ecc3888ca --- /dev/null +++ b/app/widget/timelinewidget/timelineundo.cpp @@ -0,0 +1,1664 @@ +/*** + + 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 new file mode 100644 index 000000000..d56d8dc51 --- /dev/null +++ b/app/widget/timelinewidget/timelineundo.h @@ -0,0 +1,725 @@ +/*** + + 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 . + +***/ + +#ifndef TIMELINEUNDOABLE_H +#define TIMELINEUNDOABLE_H + +#include + +#include "node/block/block.h" +#include "node/block/gap/gap.h" +#include "node/block/transition/transition.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" + +namespace olive { + +class BlockResizeCommand : public UndoCommand { +public: + BlockResizeCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + block_(block), + new_length_(new_length) + { + } + + virtual Project* GetRelevantProject() const override + { + return block_->parent()->project(); + } + +protected: + virtual void redo_internal() override + { + old_length_ = block_->length(); + block_->set_length_and_media_out(new_length_); + } + + virtual void undo_internal() override + { + block_->set_length_and_media_out(old_length_); + } + +private: + Block* block_; + rational old_length_; + rational new_length_; + +}; + +class BlockResizeWithMediaInCommand : public UndoCommand { +public: + BlockResizeWithMediaInCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + block_(block), + new_length_(new_length) + { + } + + virtual Project* GetRelevantProject() const override + { + return block_->parent()->project(); + } + +protected: + virtual void redo_internal() override + { + old_length_ = block_->length(); + block_->set_length_and_media_in(new_length_); + } + + virtual void undo_internal() override + { + block_->set_length_and_media_in(old_length_); + } + +private: + Block* block_; + rational old_length_; + rational new_length_; +}; + +class BlockTrimCommand : public UndoCommand { +public: + BlockTrimCommand(Track *track, Block* block, rational new_length, Timeline::MovementMode mode, QUndoCommand* command = nullptr); + + virtual Project* GetRelevantProject() const override; + + void SetTrimIsARollEdit(bool e) + { + trim_is_a_roll_edit_ = e; + } + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + Block* block_; + rational old_length_; + rational new_length_; + Timeline::MovementMode mode_; + + Block* adjacent_; + bool we_created_adjacent_; + bool we_deleted_adjacent_; + + bool trim_is_a_roll_edit_; + + QObject memory_manager_; + +}; + +class BlockSetMediaInCommand : public UndoCommand { +public: + BlockSetMediaInCommand(Block* block, rational new_media_in, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + block_(block), + new_media_in_(new_media_in) + { + } + + virtual Project* GetRelevantProject() const override + { + return block_->parent()->project(); + } + +protected: + virtual void redo_internal() override + { + old_media_in_ = block_->media_in(); + block_->set_media_in(new_media_in_); + } + + virtual void undo_internal() override + { + block_->set_media_in(old_media_in_); + } + +private: + Block* block_; + rational old_media_in_; + rational new_media_in_; +}; + +class TrackRippleRemoveBlockCommand : public UndoCommand { +public: + TrackRippleRemoveBlockCommand(Track* track, Block* block, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + track_(track), + block_(block) + { + } + + virtual Project* GetRelevantProject() const override + { + return track_->parent()->project(); + } + +protected: + virtual void redo_internal() override + { + before_ = block_->previous(); + track_->RippleRemoveBlock(block_); + } + + virtual void undo_internal() override + { + if (before_) { + track_->InsertBlockAfter(block_, before_); + } else { + track_->PrependBlock(block_); + } + } + +private: + Track* track_; + + Block* block_; + + Block* before_; + +}; + +class TrackPrependBlockCommand : public UndoCommand { +public: + TrackPrependBlockCommand(Track* track, Block* block, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + Block* block_; +}; + +class TrackInsertBlockAfterCommand : public UndoCommand { +public: + TrackInsertBlockAfterCommand(Track* track, Block* block, Block* before, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + track_(track), + block_(block), + before_(before) + { + } + + virtual Project* GetRelevantProject() const override + { + return block_->parent()->project(); + } + +protected: + virtual void redo_internal() override + { + track_->InsertBlockAfter(block_, before_); + } + + virtual void undo_internal() override + { + track_->RippleRemoveBlock(block_); + } + +private: + Track* track_; + + Block* block_; + + Block* before_; +}; + +/** + * @brief Clears the area between in and out + * + * The area between `in` and `out` is guaranteed to be freed. BLocks are trimmed and removed to free this space. + * By default, nothing takes this area meaning all subsequent clips are pushed backward, however you can specify + * a block to insert at the `in` point. No checking is done to ensure `insert` is the same length as `in` to `out`. + */ +class TrackRippleRemoveAreaCommand : public UndoCommand { +public: + TrackRippleRemoveAreaCommand(Track* track, rational in, rational out, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + void SetInsert(Block* insert); + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +protected: + Project* project_; + + Track* track_; + rational in_; + rational out_; + + 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_; + + QVector remove_block_commands_; + +}; + +class TrackListRippleRemoveAreaCommand : public UndoCommand { +public: + TrackListRippleRemoveAreaCommand(TrackList* list, rational in, rational out, QUndoCommand* parent = nullptr); + + virtual ~TrackListRippleRemoveAreaCommand() override; + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + TrackList* list_; + + QList working_tracks_; + + rational in_; + + rational out_; + + bool all_tracks_unlocked_; + + QVector commands_; + +}; + +class TimelineRippleRemoveAreaCommand : public UndoCommand { +public: + TimelineRippleRemoveAreaCommand(ViewerOutput* timeline, rational in, rational out, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + ViewerOutput* timeline_; + +}; + +class TrackListRippleToolCommand : public UndoCommand { +public: + struct RippleInfo { + Block* block; + Block* ref_block; + Track* track; + rational new_length; + rational old_length; + }; + + TrackListRippleToolCommand(TrackList* track_list, + const QList& info, + const Timeline::MovementMode& movement_mode, + QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + TrackList* track_list_; + + QList info_; + Timeline::MovementMode movement_mode_; + + struct WorkingData { + GapBlock* created_gap; + Block* removed_gap_after; + }; + + QVector working_data_; + + QObject memory_manager_; + + bool all_tracks_unlocked_; + +}; + +/** + * @brief Destructively places `block` at the in point `start` + * + * The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are + * 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 { +public: + TrackPlaceBlockCommand(TrackList *timeline, int track, Block* block, rational in, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + TrackList* timeline_; + int track_index_; + bool append_; + GapBlock* gap_; + QVector added_tracks_; + +}; + +class BlockSplitCommand : public UndoCommand { +public: + BlockSplitCommand(Track* track, Block* block, rational point, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + Block* new_block(); + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + Block* block_; + Block* new_block_; + + rational new_length_; + rational old_length_; + rational point_; + + QList transitions_to_move_; + + QObject memory_manager_; + + QUndoCommand* add_command_; + +}; + +class TrackSplitAtTimeCommand : public UndoCommand { +public: + TrackSplitAtTimeCommand(Track* track, rational point, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + Track* track_; + +}; + +class BlockSplitPreservingLinksCommand : public UndoCommand { +public: + BlockSplitPreservingLinksCommand(const QVector &blocks, const QList& times, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + QVector blocks_; + + QList times_; +}; + +/** + * @brief Replaces Block `old` with Block `replace` + * + * Both blocks must have equal lengths. + */ +class TrackReplaceBlockCommand : public UndoCommand { +public: + TrackReplaceBlockCommand(Track* track, Block* old, Block* replace, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + Block* old_; + Block* replace_; +}; + +class TrackReplaceBlockWithGapCommand : public UndoCommand { +public: + TrackReplaceBlockWithGapCommand(Track* track, Block* block, QUndoCommand* command = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + Block* block_; + + GapBlock* existing_gap_; + GapBlock* existing_merged_gap_; + bool existing_gap_precedes_; + GapBlock* our_gap_; + + QObject memory_manager_; + +}; + +class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand { +public: + TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + ViewerOutput* timeline_; + TimeRangeList regions_; + + QList commands_; + +}; + +class WorkareaSetEnabledCommand : public UndoCommand { +public: + WorkareaSetEnabledCommand(Project *project, TimelinePoints* points, bool enabled, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Project* project_; + + TimelinePoints* points_; + + bool old_enabled_; + + bool new_enabled_; + +}; + +class WorkareaSetRangeCommand : public UndoCommand { +public: + WorkareaSetRangeCommand(Project *project, TimelinePoints* points, const TimeRange& range, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Project* project_; + + TimelinePoints* points_; + + TimeRange old_range_; + + TimeRange new_range_; + +}; + +class BlockLinkManyCommand : public UndoCommand { +public: + BlockLinkManyCommand(const QVector blocks, bool link, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + QVector blocks_; + +}; + +class BlockLinkCommand : public UndoCommand { +public: + BlockLinkCommand(Block* a, Block* b, bool link, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Block* a_; + + Block* b_; + + bool link_; + + bool done_; + +}; + +class BlockUnlinkAllCommand : public UndoCommand { +public: + BlockUnlinkAllCommand(Block* block, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Block* block_; + + QVector unlinked_; + +}; + +class BlockEnableDisableCommand : public UndoCommand { +public: + BlockEnableDisableCommand(Block* block, bool enabled, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Block* block_; + + bool old_enabled_; + + bool new_enabled_; + +}; + +class TrackSlideCommand : public UndoCommand { +public: + TrackSlideCommand(Track* track, const QList& moving_blocks, Block* in_adjacent, Block* out_adjacent, const rational& movement, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + void slide_internal(bool undo); + + Track* track_; + QList blocks_; + rational movement_; + + bool we_created_in_adjacent_; + Block* in_adjacent_; + bool we_created_out_adjacent_; + Block* out_adjacent_; + + QObject memory_manager_; + +}; + +class TrackListInsertGaps : public UndoCommand { +public: + TrackListInsertGaps(TrackList* track_list, const rational& point, const rational& length, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + TrackList* track_list_; + + rational point_; + + rational length_; + + QList working_tracks_; + + bool all_tracks_unlocked_; + + QList gaps_to_extend_; + + QList gaps_added_; + + BlockSplitPreservingLinksCommand* split_command_; + + QObject memory_manager_; + +}; + +class TransitionRemoveCommand : public UndoCommand { +public: + TransitionRemoveCommand(Track *track, TransitionBlock* block, QUndoCommand *parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + +private: + Track* track_; + + TransitionBlock* block_; + + Block* out_block_; + Block* in_block_; + +}; + +class TimelineWidget; + +class TimelineSetSelectionsCommand : public QUndoCommand { +public: + TimelineSetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, QUndoCommand* parent = nullptr); + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineWidget* timeline_; + TimelineWidgetSelections old_; + TimelineWidgetSelections now_; + +}; + +} + +#endif // TIMELINEUNDOABLE_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index a21f022f6..f2354d419 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -220,12 +220,9 @@ void TimelineWidget::ScaleChangedEvent(const double &scale) void TimelineWidget::ConnectNodeInternal(ViewerOutput *n) { - connect(n, &ViewerOutput::BlockAdded, this, &TimelineWidget::AddBlock); - connect(n, &ViewerOutput::BlockRemoved, this, &TimelineWidget::RemoveBlock); connect(n, &ViewerOutput::TrackAdded, this, &TimelineWidget::AddTrack); connect(n, &ViewerOutput::TrackRemoved, this, &TimelineWidget::RemoveTrack); connect(n, &ViewerOutput::TimebaseChanged, this, &TimelineWidget::SetTimebase); - connect(n, &ViewerOutput::TrackHeightChanged, this, &TimelineWidget::TrackHeightChanged); ruler()->SetPlaybackCache(n->video_frame_cache()); @@ -241,20 +238,18 @@ void TimelineWidget::ConnectNodeInternal(ViewerOutput *n) view->ConnectTrackList(track_list); // Defer to the track to make all the block UI items necessary - foreach (Track* track, n->track_list(track_type)->GetTracks()) { - AddTrack(track, track_type); + const QVector tracks = n->track_list(track_type)->GetTracks(); + foreach (Track* track, tracks) { + AddTrack(track); } } } void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n) { - disconnect(n, &ViewerOutput::BlockAdded, this, &TimelineWidget::AddBlock); - disconnect(n, &ViewerOutput::BlockRemoved, this, &TimelineWidget::RemoveBlock); disconnect(n, &ViewerOutput::TrackAdded, this, &TimelineWidget::AddTrack); disconnect(n, &ViewerOutput::TrackRemoved, this, &TimelineWidget::RemoveTrack); disconnect(n, &ViewerOutput::TimebaseChanged, this, &TimelineWidget::SetTimebase); - disconnect(n, &ViewerOutput::TrackHeightChanged, this, &TimelineWidget::TrackHeightChanged); DeselectAll(); @@ -280,9 +275,7 @@ void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void QVector& selected = *static_cast*>(userdata); rational earliest_in = RATIONAL_MAX; - foreach (Block* item, selected) { - Block* block = item->block(); - + foreach (Block* block, selected) { earliest_in = qMin(earliest_in, block->in()); } @@ -292,12 +285,9 @@ void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(block))); writer->writeAttribute(QStringLiteral("in"), (block->in() - earliest_in).toString()); - Track* track = GetTrackFromBlock(block); - - if (track) { - writer->writeAttribute(QStringLiteral("tracktype"), QString::number(track->track_type())); - writer->writeAttribute(QStringLiteral("trackindex"), QString::number(track->Index())); - } + Track* track = block->track(); + writer->writeAttribute(QStringLiteral("tracktype"), QString::number(track->type())); + writer->writeAttribute(QStringLiteral("trackindex"), QString::number(track->Index())); writer->writeEndElement(); } @@ -334,10 +324,10 @@ void TimelineWidget::SelectAll() { QVector newly_selected_blocks; - for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) { - if (!selected_blocks_.contains(it.key())) { - newly_selected_blocks.append(it.key()); - AddSelection(it.key()->range(), it.value()->Track()); + foreach (Block* block, added_blocks_) { + if (!selected_blocks_.contains(block)) { + newly_selected_blocks.append(block); + AddSelection(block); } } @@ -384,7 +374,7 @@ void TimelineWidget::SplitAtPlayhead() rational playhead_time = Timecode::timestamp_to_time(GetTimestamp(), timebase()); - QVector selected_blocks = GetSelectedBlocks(); + QVector selected_blocks = GetSelectedBlocks(); // Prioritize blocks that are selected and overlap the playhead QVector blocks_to_split; @@ -400,8 +390,8 @@ void TimelineWidget::SplitAtPlayhead() bool selected = false; // See if this block is selected - foreach (TimelineViewBlockItem* item, selected_blocks) { - if (item->block() == b) { + foreach (Block* item, selected_blocks) { + if (item == b) { some_blocks_are_selected = true; selected = true; break; @@ -430,8 +420,8 @@ void TimelineWidget::SplitAtPlayhead() } void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, - bool remove_from_graph, - QUndoCommand *command) + bool remove_from_graph, + QUndoCommand *command) { foreach (Block* b, blocks) { if (b->type() == Block::kGap) { @@ -440,7 +430,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, continue; } - Track* original_track = Track::TrackFromBlock(b); + Track* original_track = b->track(); new TrackReplaceBlockWithGapCommand(original_track, b, command); @@ -452,12 +442,10 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, void TimelineWidget::DeleteSelected(bool ripple) { - QVector selected_list = GetSelectedBlocks(); + QVector selected_list = GetSelectedBlocks(); QVector blocks_to_delete; - foreach (TimelineViewBlockItem* item, selected_list) { - Block* b = item->block(); - + foreach (Block* b, selected_list) { blocks_to_delete.append(b); } @@ -481,7 +469,7 @@ void TimelineWidget::DeleteSelected(bool ripple) // For transitions, remove them but extend their attached blocks to fill their place foreach (TransitionBlock* transition, transitions_to_delete) { - new TransitionRemoveCommand(Track::TrackFromBlock(transition), + new TransitionRemoveCommand(transition->track(), transition, command); @@ -536,35 +524,35 @@ void TimelineWidget::DecreaseTrackHeight() } } -void TimelineWidget::InsertFootageAtPlayhead(const QList& footage) +void TimelineWidget::InsertFootageAtPlayhead(const QVector& footage) { import_tool_->PlaceAt(footage, GetTime(), true); } -void TimelineWidget::OverwriteFootageAtPlayhead(const QList &footage) +void TimelineWidget::OverwriteFootageAtPlayhead(const QVector &footage) { import_tool_->PlaceAt(footage, GetTime(), false); } void TimelineWidget::ToggleLinksOnSelected() { - QVector sel = GetSelectedBlocks(); + QVector sel = GetSelectedBlocks(); QVector blocks; bool link = true; - foreach (TimelineViewBlockItem* item, sel) { + foreach (Block* item, sel) { // Only clips can be linked - if (item->block()->type() != Block::kClip) { + if (item->type() != Block::kClip) { continue; } // Prioritize unlinking, if any block has links, assume we're unlinking - if (link && item->block()->HasLinks()) { + if (link && item->HasLinks()) { link = false; } - blocks.append(item->block()); + blocks.append(item); } if (blocks.isEmpty()) { @@ -580,7 +568,7 @@ void TimelineWidget::CopySelected(bool cut) return; } - QVector selected = GetSelectedBlocks(); + QVector selected = GetSelectedBlocks(); if (selected.isEmpty()) { return; @@ -588,9 +576,7 @@ void TimelineWidget::CopySelected(bool cut) QVector selected_nodes; - foreach (TimelineViewBlockItem* item, selected) { - Node* block = item->block(); - + foreach (Block* block, selected) { selected_nodes.append(block); QVector deps = block->GetDependencies(); @@ -675,7 +661,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) gap, command); - new TrackPlaceBlockCommand(GetConnectedNode()->track_list(track->track_type()), + new TrackPlaceBlockCommand(GetConnectedNode()->track_list(track->type()), track->Index(), gap, GetConnectedTimelinePoints()->workarea()->in(), @@ -699,7 +685,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) void TimelineWidget::ToggleSelectedEnabled() { - QVector items = GetSelectedBlocks(); + QVector items = GetSelectedBlocks(); if (items.isEmpty()) { return; @@ -707,29 +693,18 @@ void TimelineWidget::ToggleSelectedEnabled() QUndoCommand* command = new QUndoCommand(); - foreach (TimelineViewBlockItem* i, items) { - new BlockEnableDisableCommand(i->block(), - !i->block()->is_enabled(), + foreach (Block* i, items) { + new BlockEnableDisableCommand(i, + !i->is_enabled(), command); } Core::instance()->undo_stack()->pushIfHasChildren(command); } -QVector TimelineWidget::GetSelectedBlocks() -{ - QVector list(selected_blocks_.size()); - - for (int i=0; itrack_list(static_cast(i)), earliest_point, insert_length, @@ -737,17 +712,17 @@ void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational } } -Track *TimelineWidget::GetTrackFromReference(const TrackReference &ref) const +Track *TimelineWidget::GetTrackFromReference(const Track::Reference &ref) const { return GetConnectedNode()->track_list(ref.type())->GetTrackAt(ref.index()); } -int TimelineWidget::GetTrackY(const TrackReference &ref) +int TimelineWidget::GetTrackY(const Track::Reference &ref) { return views_.at(ref.type())->view()->GetTrackY(ref.index()); } -int TimelineWidget::GetTrackHeight(const TrackReference &ref) +int TimelineWidget::GetTrackHeight(const Track::Reference &ref) { return views_.at(ref.type())->view()->GetTrackHeight(ref.index()); } @@ -804,7 +779,6 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event) if (hover_tool) { hover_tool->HoverMove(event); - UpdateViewports(); } } } @@ -851,35 +825,15 @@ void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event) UpdateViewports(); } -void TimelineWidget::AddBlock(Block *block, TrackReference track) +void TimelineWidget::AddBlock(Block *block) { // Set up clip with view parameters (clip item will automatically size its rect accordingly) - TimelineViewBlockItem* item = block_items_.value(block); - - if (!item) { - - // Add to list of clip items that can be iterated through - item = new TimelineViewBlockItem(block); - block_items_.insert(block, item); - - // Set scale parameters - item->SetScale(GetScale()); - item->SetTimebase(timebase()); - item->SetYCoords(GetTrackY(track), GetTrackHeight(track)); - item->SetTrack(track); - - // Add item to graphics scene - views_.at(track.type())->view()->scene()->addItem(item); - + if (!added_blocks_.contains(block)) { connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); - } else if (item->Track() != track) { - - item->SetYCoords(GetTrackY(track), GetTrackHeight(track)); - item->SetTrack(track); - + added_blocks_.append(block); } } @@ -891,84 +845,54 @@ void TimelineWidget::RemoveBlock(Block *b) disconnect(b, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); // Take item from map - TimelineViewBlockItem* item = block_items_.take(b); + added_blocks_.removeOne(b); // If selected, deselect it int select_index = selected_blocks_.indexOf(b); if (select_index > -1) { selected_blocks_.removeAt(select_index); - RemoveSelection(item); + RemoveSelection(b); + + emit BlocksDeselected({b}); } - - // Finally, delete item - delete item; - - emit BlocksDeselected({b}); } -void TimelineWidget::AddTrack(Track *track, Track::Type type) +void TimelineWidget::AddTrack(Track *track) { foreach (Block* b, track->Blocks()) { - AddBlock(b, TrackReference(type, track->Index())); + AddBlock(b); } - connect(track, &Track::IndexChanged, this, &TimelineWidget::TrackIndexChanged); - connect(track, &Track::PreviewChanged, this, &TimelineWidget::TrackPreviewUpdated); + connect(track, &Track::IndexChanged, this, &TimelineWidget::TrackUpdated); + connect(track, &Track::PreviewChanged, this, &TimelineWidget::TrackUpdated); + connect(track, &Track::BlocksRefreshed, this, &TimelineWidget::TrackUpdated); + connect(track, &Track::TrackHeightChangedInPixels, this, &TimelineWidget::TrackUpdated); + connect(track, &Track::BlockAdded, this, &TimelineWidget::AddBlock); + connect(track, &Track::BlockRemoved, this, &TimelineWidget::RemoveBlock); } void TimelineWidget::RemoveTrack(Track *track) { - disconnect(track, &Track::IndexChanged, this, &TimelineWidget::TrackIndexChanged); - disconnect(track, &Track::PreviewChanged, this, &TimelineWidget::TrackPreviewUpdated); + disconnect(track, &Track::IndexChanged, this, &TimelineWidget::TrackUpdated); + disconnect(track, &Track::PreviewChanged, this, &TimelineWidget::TrackUpdated); + disconnect(track, &Track::BlocksRefreshed, this, &TimelineWidget::TrackUpdated); + disconnect(track, &Track::TrackHeightChangedInPixels, this, &TimelineWidget::TrackUpdated); + disconnect(track, &Track::BlockAdded, this, &TimelineWidget::AddBlock); + disconnect(track, &Track::BlockRemoved, this, &TimelineWidget::RemoveBlock); foreach (Block* b, track->Blocks()) { RemoveBlock(b); } } -void TimelineWidget::TrackIndexChanged() +void TimelineWidget::TrackUpdated() { - Track* track = static_cast(sender()); - TrackReference ref(track->track_type(), track->Index()); - - foreach (Block* b, track->Blocks()) { - TimelineViewBlockItem* item = block_items_.value(b); - - item->SetYCoords(GetTrackY(ref), GetTrackHeight(ref)); - item->SetTrack(ref); - } -} - -void TimelineWidget::BlockRefreshed() -{ - TimelineViewRect* rect = block_items_.value(static_cast(sender())); - - if (rect) { - rect->UpdateRect(); - } + UpdateViewports(static_cast(sender())->type()); } void TimelineWidget::BlockUpdated() { - TimelineViewRect* rect = block_items_.value(static_cast(sender())); - - if (rect) { - rect->update(); - } -} - -void TimelineWidget::TrackPreviewUpdated() -{ - QMap::const_iterator i; - - Track* track = static_cast(sender()); - TrackReference track_ref(track->track_type(), track->Index()); - - for (i=block_items_.constBegin(); i!=block_items_.constEnd(); i++) { - if (i.value()->Track() == track_ref) { - i.value()->update(); - } - } + UpdateViewports(static_cast(sender())->track()->type()); } void TimelineWidget::UpdateHorizontalSplitters() @@ -993,29 +917,11 @@ void TimelineWidget::UpdateTimecodeWidthFromSplitters(QSplitter* s) timecode_label_->setFixedWidth(s->sizes().first() + s->handleWidth()); } -void TimelineWidget::TrackHeightChanged(Track::Type type, int index, int height) -{ - Q_UNUSED(index) - Q_UNUSED(height) - - QMap::const_iterator iterator; - TimelineView* view = views_.at(type)->view(); - - for (iterator=block_items_.begin();iterator!=block_items_.end();iterator++) { - TimelineViewBlockItem* block_item = iterator.value(); - - if (block_item->Track().type() == type) { - block_item->SetYCoords(view->GetTrackY(block_item->Track().index()), - view->GetTrackHeight(block_item->Track().index())); - } - } -} - void TimelineWidget::ShowContextMenu() { Menu menu(this); - QVector selected = GetSelectedBlocks(); + QVector selected = GetSelectedBlocks(); if (!selected.isEmpty()) { MenuShared::instance()->AddItemsForEditMenu(&menu, true); @@ -1024,11 +930,11 @@ void TimelineWidget::ShowContextMenu() QAction* properties_action = menu.addAction(tr("Properties")); connect(properties_action, &QAction::triggered, this, [this](){ - QVector block_items = GetSelectedBlocks(); + QVector block_items = GetSelectedBlocks(); QVector nodes; - foreach (TimelineViewBlockItem* i, block_items) { - nodes.append(i->block()); + foreach (Block* i, block_items) { + nodes.append(i); } Core::instance()->LabelNodes(nodes); @@ -1082,7 +988,7 @@ void TimelineWidget::SetViewTimestamp(const int64_t &ts) for (int i=0;iview()->SetTime(Timecode::rescale_timestamp(ts, timebase(), GetConnectedNode()->audio_params().time_base())); @@ -1094,7 +1000,7 @@ void TimelineWidget::SetViewTimestamp(const int64_t &ts) void TimelineWidget::ViewTimestampChanged(int64_t ts) { - if (use_audio_time_units_ && sender() == views_.at(Timeline::kTrackTypeAudio)) { + if (use_audio_time_units_ && sender() == views_.at(Track::kAudio)) { ts = Timecode::rescale_timestamp(ts, GetConnectedNode()->audio_params().time_base(), timebase()); @@ -1124,7 +1030,7 @@ void TimelineWidget::UpdateViewTimebases() for (int i=0;iview()->SetTimebase(GetConnectedNode()->audio_params().time_base()); } else { view->view()->SetTimebase(timebase()); @@ -1141,17 +1047,11 @@ void TimelineWidget::SetViewBeamCursor(const TimelineCoordinate &coord) void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected) { - TimelineViewBlockItem* link_item; - foreach (Block* link, block->linked_clips()) { - link_item = block_items_.value(link); - - if (link_item) { - if (selected) { - AddSelection(link_item); - } else { - RemoveSelection(link_item); - } + if (selected) { + AddSelection(link); + } else { + RemoveSelection(link); } } } @@ -1357,7 +1257,7 @@ void TimelineWidget::EditTo(Timeline::MovementMode mode) Core::instance()->undo_stack()->pushIfHasChildren(command); } -void TimelineWidget::ShowSnap(const QList ×) +void TimelineWidget::ShowSnap(const QVector ×) { foreach (TimelineAndTrackView* tview, views_) { tview->view()->EnableSnap(times); @@ -1366,7 +1266,7 @@ void TimelineWidget::ShowSnap(const QList ×) void TimelineWidget::UpdateViewports(const Track::Type &type) { - if (type == Timeline::kTrackTypeNone) { + if (type == Track::kNone) { foreach (TimelineAndTrackView* tview, views_) { tview->view()->viewport()->update(); } @@ -1375,6 +1275,46 @@ void TimelineWidget::UpdateViewports(const Track::Type &type) } } +QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const QPoint& p2) +{ + QVector blocks_in_rect; + + // Determine which tracks are in the rect + for (int i=0; iview(); + + // Map global mouse coordinates to viewport + QRectF mapped_rect(view->mapToScene(view->viewport()->mapFromGlobal(p1)), + view->mapToScene(view->viewport()->mapFromGlobal(p2))); + + // Normalize + mapped_rect = mapped_rect.normalized(); + + // Get tracks + TrackList* track_list = GetConnectedNode()->track_list(static_cast(i)); + + for (int j=0; jGetTrackCount(); j++) { + int track_top = view->GetTrackY(j); + int track_bottom = track_top + view->GetTrackHeight(j); + + if (!(track_bottom < mapped_rect.top() || track_top > mapped_rect.bottom())) { + // This track is in the rect, so we'll iterate through its blocks and see where they start + rational left_time = SceneToTime(mapped_rect.left()); + rational right_time = SceneToTime(mapped_rect.right(), true); + + Track* track = track_list->GetTrackAt(j); + foreach (Block* b, track->Blocks()) { + if (!(b->out() < left_time || b->in() > right_time)) { + blocks_in_rect.append(b); + } + } + } + } + } + + return blocks_in_rect; +} + void TimelineWidget::HideSnaps() { foreach (TimelineAndTrackView* tview, views_) { @@ -1417,21 +1357,8 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin return; } - QList items_in_rubberband; - - // Determine all items in the rubberband - foreach (TimelineAndTrackView* tview, views_) { - TimelineView* view = tview->view(); - - // Map global mouse coordinates to viewport - QRect mapped_rect(view->viewport()->mapFromGlobal(drag_origin_), - view->viewport()->mapFromGlobal(rubberband_now)); - - // Normalize and get items in rect - QList rubberband_items = view->items(mapped_rect.normalized()); - - items_in_rubberband.append(rubberband_items); - } + // Get current items in rubberband + QVector items_in_rubberband = GetBlocksInGlobalRect(drag_origin_, rubberband_now); // Reset selection to whatever it was before SetSelections(rubberband_old_selections_); @@ -1439,32 +1366,26 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin // Add any blocks in rubberband rubberband_now_selected_.clear(); - foreach (QGraphicsItem* item, items_in_rubberband) { - TimelineViewBlockItem* block_item = dynamic_cast(item); + foreach (Block* b, items_in_rubberband) { + if (b->type() == Block::kGap) { + continue; + } - if (block_item) { - Block* b = block_item->block(); + Track* t = b->track(); + if (t->IsLocked()) { + continue; + } - if (b->type() == Block::kGap) { - continue; - } + if (!rubberband_now_selected_.contains(b)) { + AddSelection(b); + rubberband_now_selected_.append(b); + } - Track* t = GetTrackFromReference(block_item->Track()); - if (t && t->IsLocked()) { - continue; - } - - if (!rubberband_now_selected_.contains(b)) { - AddSelection(block_item); - rubberband_now_selected_.append(b); - } - - if (select_links) { - foreach (Block* link, b->linked_clips()) { - if (!rubberband_now_selected_.contains(link)) { - AddSelection(block_items_.value(link)); - rubberband_now_selected_.append(link); - } + if (select_links) { + foreach (Block* link, b->linked_clips()) { + if (!rubberband_now_selected_.contains(link)) { + AddSelection(link); + rubberband_now_selected_.append(link); } } } @@ -1482,7 +1403,7 @@ void TimelineWidget::EndRubberBandSelect() rubberband_old_selections_.clear(); } -void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &track) +void TimelineWidget::AddSelection(const TimeRange &time, const Track::Reference &track) { selections_[track].insert(time); @@ -1491,10 +1412,10 @@ void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &t void TimelineWidget::AddSelection(Block *item) { - AddSelection(item->block()->range(), item->Track()); + AddSelection(item->range(), item->track()->ToReference()); } -void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference &track) +void TimelineWidget::RemoveSelection(const TimeRange &time, const Track::Reference &track) { selections_[track].remove(time); @@ -1503,7 +1424,7 @@ void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference void TimelineWidget::RemoveSelection(Block *item) { - RemoveSelection(item->block()->range(), item->Track()); + RemoveSelection(item->range(), item->track()->ToReference()); } void TimelineWidget::SetSelections(const TimelineWidgetSelections &s) @@ -1515,14 +1436,13 @@ void TimelineWidget::SetSelections(const TimelineWidgetSelections &s) Block *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord) { - for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) { - Block* b = it.key(); - TimelineViewBlockItem* item = it.value(); + Track* track = GetTrackFromReference(coord.GetTrack()); + foreach (Block* b, added_blocks_) { if (b->in() <= coord.GetFrame() && b->out() > coord.GetFrame() - && item->Track() == coord.GetTrack()) { - return item; + && b->track() == track) { + return b; } } @@ -1534,13 +1454,13 @@ struct SnapData { rational movement; }; -QList AttemptSnap(const QList& screen_pt, - double compare_pt, - const QList& start_times, - const rational& compare_time) { +QVector AttemptSnap(const QVector& screen_pt, + double compare_pt, + const QVector& start_times, + const rational& compare_time) { const qreal kSnapRange = 10; // FIXME: Hardcoded number - QList snap_data; + QVector snap_data; for (int i=0;i AttemptSnap(const QList& screen_pt, return snap_data; } -bool TimelineWidget::SnapPoint(QList start_times, rational* movement, int snap_points) +bool TimelineWidget::SnapPoint(QVector start_times, rational* movement, int snap_points) { - QList screen_pt; + QVector screen_pt; foreach (const rational& s, start_times) { screen_pt.append(TimeToScene(s + *movement)); } - QList potential_snaps; + QVector potential_snaps; if (snap_points & kSnapToPlayhead) { rational playhead_abs_time = GetTime(); @@ -1569,21 +1489,15 @@ bool TimelineWidget::SnapPoint(QList start_times, rational* movement, } if (snap_points & kSnapToClips) { - QMap::const_iterator i; + foreach (Block* b, added_blocks_) { + qreal rect_left = TimeToScene(b->in()); + qreal rect_right = TimeToScene(b->out()); - for (i=block_items_.constBegin(); i!=block_items_.constEnd(); i++) { - TimelineViewBlockItem* item = i.value(); + // Attempt snapping to clip in point + potential_snaps.append(AttemptSnap(screen_pt, rect_left, start_times, b->in())); - if (item) { - qreal rect_left = item->x(); - qreal rect_right = rect_left + item->rect().width(); - - // Attempt snapping to clip in point - potential_snaps.append(AttemptSnap(screen_pt, rect_left, start_times, item->block()->in())); - - // Attempt snapping to clip out point - potential_snaps.append(AttemptSnap(screen_pt, rect_right, start_times, item->block()->out())); - } + // Attempt snapping to clip out point + potential_snaps.append(AttemptSnap(screen_pt, rect_right, start_times, b->out())); } } @@ -1620,7 +1534,7 @@ bool TimelineWidget::SnapPoint(QList start_times, rational* movement, *movement = potential_snaps.at(closest_snap).movement; // Find all points at this movement - QList snap_times; + QVector snap_times; foreach (const SnapData& d, potential_snaps) { if (d.movement == *movement) { snap_times.append(d.time); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 627e7cb69..63cf41995 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -75,9 +75,9 @@ public: void DecreaseTrackHeight(); - void InsertFootageAtPlayhead(const QList &footage); + void InsertFootageAtPlayhead(const QVector &footage); - void OverwriteFootageAtPlayhead(const QList &footage); + void OverwriteFootageAtPlayhead(const QVector &footage); void ToggleLinksOnSelected(); @@ -94,17 +94,7 @@ public: return selected_blocks_; } - Track* GetTrackFromBlock(Block* block) const - { - return GetTrackFromReference(GetTrackReferenceFromBlock(block)); - } - - TrackReference GetTrackReferenceFromBlock(Block* block) const - { - return track_lookup_.value(block); - } - - virtual bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll) override; + virtual bool SnapPoint(QVector start_times, rational *movement, int snap_points = kSnapAll) override; virtual void HideSnaps() override; @@ -122,10 +112,10 @@ public: */ Block* GetItemAtScenePos(const TimelineCoordinate &coord); - void AddSelection(const TimeRange& time, const TrackReference& track); + void AddSelection(const TimeRange& time, const Track::Reference& track); void AddSelection(Block* item); - void RemoveSelection(const TimeRange& time, const TrackReference& track); + void RemoveSelection(const TimeRange& time, const Track::Reference& track); void RemoveSelection(Block* item); const TimelineWidgetSelections& GetSelections() const @@ -135,7 +125,7 @@ public: void SetSelections(const TimelineWidgetSelections &s); - Track* GetTrackFromReference(const TrackReference& ref) const; + Track* GetTrackFromReference(const Track::Reference& ref) const; void SetViewBeamCursor(const TimelineCoordinate& coord); @@ -150,8 +140,8 @@ public: void MoveRubberBandSelect(bool enable_selecting, bool select_links); void EndRubberBandSelect(); - int GetTrackY(const TrackReference& ref); - int GetTrackHeight(const TrackReference& ref); + int GetTrackY(const Track::Reference& ref); + int GetTrackHeight(const Track::Reference& ref); void AddGhost(TimelineViewGhostItem* ghost); @@ -237,10 +227,12 @@ private: void EditTo(Timeline::MovementMode mode); - void ShowSnap(const QList& times); + void ShowSnap(const QVector& times); void UpdateViewports(const Track::Type& type = Track::kNone); + QVector GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2); + QPoint drag_origin_; QRubberBand rubberband_; @@ -259,14 +251,14 @@ private: QVector ghost_items_; - QHash track_lookup_; - - QList views_; + QVector views_; TimeSlider* timecode_label_; QVector selected_blocks_; + QVector added_blocks_; + int deferred_scroll_value_; bool use_audio_time_units_; @@ -288,31 +280,19 @@ private slots: void ViewDragLeft(QDragLeaveEvent* event); void ViewDragDropped(TimelineViewMouseEvent* event); - void AddBlock(Block* block, TrackReference track); + void AddBlock(Block* block); void RemoveBlock(Block *blocks); - void AddTrack(Track* track, Track::Type type); + void AddTrack(Track* track); void RemoveTrack(Track* track); - void TrackIndexChanged(); - - /** - * @brief Slot for when a Block node changes its parameters and the graphics need to update - * - * This slot does a static_cast on sender() to Block*, meaning all objects triggering this slot must be Blocks or - * derivatives. - */ - void BlockRefreshed(); + void TrackUpdated(); void BlockUpdated(); - void TrackPreviewUpdated(); - void UpdateHorizontalSplitters(); void UpdateTimecodeWidthFromSplitters(QSplitter *s); - void TrackHeightChanged(Track::Type type, int index, int height); - void ShowContextMenu(); void DeferredScrollAction(); diff --git a/app/widget/timelinewidget/timelinewidgetselections.cpp b/app/widget/timelinewidget/timelinewidgetselections.cpp index 1a1dbdcbf..9e73853ad 100644 --- a/app/widget/timelinewidget/timelinewidgetselections.cpp +++ b/app/widget/timelinewidget/timelinewidgetselections.cpp @@ -48,7 +48,7 @@ void TimelineWidgetSelections::ShiftTracks(Track::Type type, int diff) // Then re-insert them with the diff applied for (auto it=cached_selections.cbegin(); it!=cached_selections.cend(); it++) { - TrackReference ref(it.key().type(), it.key().index() + diff); + Track::Reference ref(it.key().type(), it.key().index() + diff); this->insert(ref, it.value()); } diff --git a/app/widget/timelinewidget/timelinewidgetselections.h b/app/widget/timelinewidget/timelinewidgetselections.h index 9df7993d1..9ad221d4d 100644 --- a/app/widget/timelinewidget/timelinewidgetselections.h +++ b/app/widget/timelinewidget/timelinewidgetselections.h @@ -24,11 +24,11 @@ #include #include "common/timerange.h" -#include "timeline/trackreference.h" +#include "node/output/track/track.h" namespace olive { -class TimelineWidgetSelections : public QHash +class TimelineWidgetSelections : public QHash { public: TimelineWidgetSelections() = default; diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 81b194f1a..d7c6e5830 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -18,14 +18,12 @@ ***/ -#include "widget/timelinewidget/timelinewidget.h" - #include "add.h" #include "core.h" #include "node/factory.h" #include "node/generator/solid/solid.h" #include "node/generator/text/text.h" -#include "widget/nodeview/nodeviewundo.h" +#include "widget/timelinewidget/timelinewidget.h" namespace olive { @@ -37,7 +35,7 @@ AddTool::AddTool(TimelineWidget *parent) : void AddTool::MousePress(TimelineViewMouseEvent *event) { - const TrackReference& track = event->GetTrack(); + const Track::Reference& track = event->GetTrack(); // Check if track is locked Track* t = parent()->GetTrackFromReference(track); @@ -89,7 +87,7 @@ void AddTool::MouseMove(TimelineViewMouseEvent *event) void AddTool::MouseRelease(TimelineViewMouseEvent *event) { - const TrackReference& track = ghost_->GetTrack(); + const Track::Reference& track = ghost_->GetTrack(); if (ghost_) { if (!ghost_->GetAdjustedLength().isNull()) { diff --git a/app/widget/timelinewidget/tool/edit.cpp b/app/widget/timelinewidget/tool/edit.cpp index f5ace14ad..199c7426c 100644 --- a/app/widget/timelinewidget/tool/edit.cpp +++ b/app/widget/timelinewidget/tool/edit.cpp @@ -80,7 +80,7 @@ void EditTool::MouseDoubleClick(TimelineViewMouseEvent *event) { Block* item = parent()->GetItemAtScenePos(event->GetCoordinates()); - if (item && !parent()->GetTrackFromBlock(item)->IsLocked()) { + if (item && !item->track()->IsLocked()) { parent()->AddSelection(item); } } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index ace9339d4..9ee4c8fd8 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -18,7 +18,7 @@ ***/ -#include "widget/timelinewidget/timelinewidget.h" +#include "import.h" #include #include @@ -192,12 +192,12 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event) } } -void ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert) { PlaceAt(FootageToDraggedFootage(footage), start, insert); } -void ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert) { dragged_footage_ = footage; @@ -209,7 +209,7 @@ void ImportTool::PlaceAt(const QList &footage, const rational &s DropGhosts(insert); } -void ImportTool::FootageToGhosts(rational ghost_start, const QList &footage_list, const rational& dest_tb, const int& track_start) +void ImportTool::FootageToGhosts(rational ghost_start, const QVector &footage_list, const rational& dest_tb, const int& track_start) { foreach (const DraggedFootage& footage, footage_list) { @@ -255,7 +255,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QListSetTrack(TrackReference(track_type, track_offsets.at(track_type))); + ghost->SetTrack(Track::Reference(track_type, track_offsets.at(track_type))); // Increment track count for this track type track_offsets[track_type]++; @@ -361,7 +361,7 @@ void ImportTool::DropGhosts(bool insert) if (behavior == kDWSAuto) { - QList footage_only; + QVector footage_only; foreach (const DraggedFootage& df, dragged_footage_) { footage_only.append(df.footage()); @@ -431,25 +431,11 @@ void ImportTool::DropGhosts(bool insert) video_input->SetStream(footage_stream); new NodeAddCommand(dst_graph, video_input, command); - TransformDistortNode* transform = new TransformDistortNode(); new NodeAddCommand(dst_graph, transform, command); new NodeEdgeAddCommand(video_input, transform->texture_input(), -1, command); new NodeEdgeAddCommand(transform, clip->texture_input(), -1, command); - - /* - MatrixGenerator* matrix = new MatrixGenerator(); - new NodeAddCommand(dst_graph, matrix, command); - - MathNode* multiply = new MathNode(); - multiply->SetOperation(MathNode::kOpMultiply); - new NodeAddCommand(dst_graph, multiply, command); - - new NodeEdgeAddCommand(video_input->output(), multiply->param_a_in(), command); - new NodeEdgeAddCommand(matrix->output(), multiply->param_b_in(), command); - new NodeEdgeAddCommand(multiply->output(), clip->texture_input(), command); - */ break; } case Stream::kAudio: @@ -503,9 +489,9 @@ ImportTool::DraggedFootage ImportTool::FootageToDraggedFootage(Footage *f) return DraggedFootage(f, f->get_enabled_stream_flags()); } -QList ImportTool::FootageToDraggedFootage(QList footage) +QVector ImportTool::FootageToDraggedFootage(QVector footage) { - QList df; + QVector df; foreach (Footage* f, footage) { df.append(FootageToDraggedFootage(f)); diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index 8c42066b0..40c653cf9 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -58,8 +58,8 @@ public: }; - void PlaceAt(const QList &footage, const rational& start, bool insert); - void PlaceAt(const QList &footage, const rational& start, bool insert); + void PlaceAt(const QVector &footage, const rational& start, bool insert); + void PlaceAt(const QVector &footage, const rational& start, bool insert); enum DropWithoutSequenceBehavior { kDWSAsk, @@ -70,15 +70,15 @@ public: private: static DraggedFootage FootageToDraggedFootage(Footage* f); - static QList FootageToDraggedFootage(QList footage); + static QVector FootageToDraggedFootage(QVector footage); - void FootageToGhosts(rational ghost_start, const QList& footage, const rational &dest_tb, const int &track_start); + void FootageToGhosts(rational ghost_start, const QVector &footage, const rational &dest_tb, const int &track_start); void PrepGhosts(const rational &frame, const int &track_index); void DropGhosts(bool insert); - QList dragged_footage_; + QVector dragged_footage_; int import_pre_buffer_; diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 6834a9f3e..afbe84efd 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -49,17 +49,19 @@ PointerTool::PointerTool(TimelineWidget *parent) : void PointerTool::MousePress(TimelineViewMouseEvent *event) { + const Track::Reference& track_ref = event->GetTrack(); + // Determine if item clicked on is selectable clicked_item_ = parent()->GetItemAtScenePos(event->GetCoordinates()); can_rubberband_select_ = false; bool selectable_item = (clicked_item_ - && !parent()->GetTrackFromReference(clicked_item_->Track())->IsLocked()); + && !parent()->GetTrackFromReference(track_ref)->IsLocked()); if (selectable_item) { // Cache the clip's type for use later - drag_track_type_ = clicked_item_->Track().type(); + drag_track_type_ = track_ref.type(); // If we haven't started dragging yet, we'll initiate a drag here // Record where the drag started in timeline coordinates @@ -73,7 +75,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) // the block is not a gap) if (drag_movement_mode_ == Timeline::kNone && movement_allowed_ - && clicked_item_->block()->type() != Block::kGap) { + && clicked_item_->type() != Block::kGap) { drag_movement_mode_ = Timeline::kMove; } @@ -86,12 +88,12 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) // If shift is held, deselect it if (event->GetModifiers() & Qt::ShiftModifier) { parent()->RemoveSelection(clicked_item_); - deselected_blocks.append(clicked_item_->block()); + deselected_blocks.append(clicked_item_); // If not holding alt, deselect all links as well if (!(event->GetModifiers() & Qt::AltModifier)) { - parent()->SetBlockLinksSelected(clicked_item_->block(), false); - deselected_blocks.append(clicked_item_->block()->linked_clips()); + parent()->SetBlockLinksSelected(clicked_item_, false); + deselected_blocks.append(clicked_item_->linked_clips()); } } @@ -113,12 +115,12 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) // Select this item parent()->AddSelection(clicked_item_); - selected_blocks.append(clicked_item_->block()); + selected_blocks.append(clicked_item_); // If not holding alt, select all links as well if (!(event->GetModifiers() & Qt::AltModifier)) { - parent()->SetBlockLinksSelected(clicked_item_->block(), true); - selected_blocks.append(clicked_item_->block()->linked_clips()); + parent()->SetBlockLinksSelected(clicked_item_, true); + selected_blocks.append(clicked_item_->linked_clips()); } parent()->SignalSelectedBlocks(selected_blocks); @@ -142,7 +144,7 @@ void PointerTool::MouseMove(TimelineViewMouseEvent *event) // If we clicked an item but are rubberband selecting anyway, deselect it now if (clicked_item_) { parent()->RemoveSelection(clicked_item_); - parent()->SignalDeselectedBlocks({clicked_item_->block()}); + parent()->SignalDeselectedBlocks({clicked_item_}); clicked_item_ = nullptr; } @@ -244,13 +246,13 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, bool slide_instead_of_moving) { // Get list of selected blocks - QVector clips = parent()->GetSelectedBlocks(); + QVector clips = parent()->GetSelectedBlocks(); if (trim_mode == Timeline::kMove) { // Each block type has different behavior, so we determine the type of the block that was // clicked and filter out any others. - Block::Type clicked_block_type = clicked_item->block()->type(); + Block::Type clicked_block_type = clicked_item->type(); // Gaps are not allowed to move, and since we only allow moving one block type at a time, // dragging a gap is a no-op @@ -269,43 +271,39 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // For slides to be legal, we make all blocks "contiguous". This means that only one series // of blocks can move at a time and prevents. - QHash earliest_block_on_track; - QHash latest_block_on_track; + QHash earliest_block_on_track; + QHash latest_block_on_track; - foreach (TimelineViewBlockItem* item, clips) { - Block* this_block = item->block(); - const TrackReference& track = item->Track(); - - Block* current_earliest = earliest_block_on_track.value(track, nullptr); + foreach (Block* this_block, clips) { + Block* current_earliest = earliest_block_on_track.value(this_block->track(), nullptr); if (!current_earliest || this_block->in() < current_earliest->in()) { - earliest_block_on_track.insert(track, item->block()); + earliest_block_on_track.insert(this_block->track(), this_block); } - Block* current_latest = latest_block_on_track.value(track, nullptr); + Block* current_latest = latest_block_on_track.value(this_block->track(), nullptr); if (!current_latest || this_block->out() > current_earliest->out()) { - latest_block_on_track.insert(track, item->block()); + latest_block_on_track.insert(this_block->track(), this_block); } } - QHash::const_iterator i; - for (i=earliest_block_on_track.constBegin(); i!=earliest_block_on_track.constEnd(); i++) { + for (auto i=earliest_block_on_track.constBegin(); i!=earliest_block_on_track.constEnd(); i++) { // Make a contiguous stream - const TrackReference& track = i.key(); + Track* track = i.key(); Block* earliest = i.value(); Block* latest = latest_block_on_track.value(i.key()); // First we add the block that's out trimming, the one prior to the earliest TimelineViewGhostItem* earliest_ghost; if (earliest->previous()) { - earliest_ghost = AddGhostFromBlock(earliest->previous(), track, Timeline::kTrimOut); + earliest_ghost = AddGhostFromBlock(earliest->previous(), Timeline::kTrimOut); } else { - earliest_ghost = AddGhostFromNull(earliest->in(), earliest->in(), track, Timeline::kTrimOut); + earliest_ghost = AddGhostFromNull(earliest->in(), earliest->in(), track->ToReference(), Timeline::kTrimOut); } SetGhostToSlideMode(earliest_ghost); // Then we add the block that's in trimming, the one after the latest if (latest->next()) { - TimelineViewGhostItem* latest_ghost = AddGhostFromBlock(latest->next(), track, Timeline::kTrimIn); + TimelineViewGhostItem* latest_ghost = AddGhostFromBlock(latest->next(), Timeline::kTrimIn); SetGhostToSlideMode(latest_ghost); } @@ -320,15 +318,13 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, b = earliest; } - TimelineViewGhostItem* between_ghost = AddGhostFromBlock(b, track, Timeline::kMove); + TimelineViewGhostItem* between_ghost = AddGhostFromBlock(b, Timeline::kMove); SetGhostToSlideMode(between_ghost); } while (b != latest); } } else { // Prepare for a standard pointer move - foreach (TimelineViewBlockItem* clip_item, clips) { - Block* block = clip_item->block(); - + foreach (Block* block, clips) { if (block->type() == Block::kGap || block->type() == Block::kTransition) { // Gaps cannot move, and we handle transitions further down continue; @@ -336,24 +332,21 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // Create ghost TimelineViewGhostItem* ghost = AddGhostFromBlock(block, - clip_item->Track(), trim_mode); Q_UNUSED(ghost) // Add transitions if this has any - TransitionBlock* opening_transition = TransitionBlock::GetBlockInTransition(block); - TransitionBlock* closing_transition = TransitionBlock::GetBlockOutTransition(block); + TransitionBlock* opening_transition = block->in_transition(); + TransitionBlock* closing_transition = block->out_transition(); if (opening_transition) { TimelineViewGhostItem* ot_ghost = AddGhostFromBlock(opening_transition, - clip_item->Track(), trim_mode); Q_UNUSED(ot_ghost) } if (closing_transition) { TimelineViewGhostItem* cl_ghost = AddGhostFromBlock(closing_transition, - clip_item->Track(), trim_mode); Q_UNUSED(cl_ghost) } @@ -368,7 +361,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode); // Create ghosts for trimming - foreach (TimelineViewBlockItem* clip_item, clips) { + foreach (Block* clip_item, clips) { if (clip_item != clicked_item && (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) { // Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We @@ -376,10 +369,10 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, continue; } - Block* block = clip_item->block(); + Block* block = clip_item; // Create ghost for this block - TimelineViewGhostItem* ghost = AddGhostFromBlock(block, clip_item->Track(), trim_mode); + TimelineViewGhostItem* ghost = AddGhostFromBlock(block, trim_mode); // If this side of the clip has a transition, we treat it more like a slide for that // transition than a trim/roll @@ -391,14 +384,14 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // Get appropriate transition for the side of the clip if (trim_mode == Timeline::kTrimIn) { - connected_transition = TransitionBlock::GetBlockInTransition(block); + connected_transition = block->in_transition(); } else { - connected_transition = TransitionBlock::GetBlockOutTransition(block); + connected_transition = block->out_transition(); } if (connected_transition) { // We found a transition, we'll make this a "slide" action - TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(connected_transition, clip_item->Track(), Timeline::kMove); + TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(connected_transition, Timeline::kMove); // This will in effect be a slide with the transition moving between two other blocks SetGhostToSlideMode(ghost); @@ -435,11 +428,11 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, TimelineViewGhostItem* adjacent_ghost; if (adjacent) { - adjacent_ghost = AddGhostFromBlock(adjacent, clip_item->Track(), flipped_mode); + adjacent_ghost = AddGhostFromBlock(adjacent, flipped_mode); } else if (trim_mode == Timeline::kTrimIn || block->next()) { rational null_ghost_pos = (trim_mode == Timeline::kTrimIn) ? block->in() : block->out(); - adjacent_ghost = AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->Track(), flipped_mode); + adjacent_ghost = AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->track()->ToReference(), flipped_mode); } else { adjacent_ghost = nullptr; } @@ -650,7 +643,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) block = static_cast(copy); } - const TrackReference& track_ref = p.ghost->GetAdjustedTrack(); + const Track::Reference& track_ref = p.ghost->GetAdjustedTrack(); new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()), track_ref.index(), block, @@ -669,13 +662,13 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Assume that the blocks are contiguous per track as set up in InitiateGhostsInternal() // All we need to do is sort them by track and order them - QHash > slide_info; - QHash in_adjacents; - QHash out_adjacents; + QHash > slide_info; + QHash in_adjacents; + QHash out_adjacents; rational movement; foreach (const GhostBlockPair& p, blocks_sliding) { - const TrackReference& track = p.ghost->GetTrack(); + const Track::Reference& track = p.ghost->GetTrack(); switch (p.ghost->GetMode()) { case Timeline::kNone: @@ -711,7 +704,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) } if (!movement.isNull()) { - QHash >::const_iterator i; + QHash >::const_iterator i; for (i=slide_info.constBegin(); i!=slide_info.constEnd(); i++) { new TrackSlideCommand(parent()->GetTrackFromReference(i.key()), i.value(), @@ -735,14 +728,18 @@ Timeline::MovementMode PointerTool::IsCursorInTrimHandle(Block *block, qreal cur { double kTrimHandle = QtUtils::QFontMetricsWidth(parent()->fontMetrics(), "H"); + double block_left = parent()->TimeToScene(block->in()); + double block_right = parent()->TimeToScene(block->out()); + double block_width = block_right - block_left; + // Block is too narrow, no trimming allowed - if (block->rect().width() <= kTrimHandle * 2) { + if (block_width <= kTrimHandle * 2) { return Timeline::kNone; } - if (trimming_allowed_ && cursor_x <= block->x() + kTrimHandle) { + if (trimming_allowed_ && cursor_x <= block_left + kTrimHandle) { return Timeline::kTrimIn; - } else if (trimming_allowed_ && cursor_x >= block->x() + block->rect().right() - kTrimHandle) { + } else if (trimming_allowed_ && cursor_x >= block_left + block_right - kTrimHandle) { return Timeline::kTrimOut; } else { return Timeline::kNone; @@ -757,7 +754,7 @@ void PointerTool::InitiateDrag(Block *clicked_item, //#define HIDE_GAP_GHOSTS -TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists) +TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, Timeline::MovementMode mode, bool check_if_exists) { if (check_if_exists) { foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { @@ -767,7 +764,7 @@ TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, const TrackR } } - TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block, track); + TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block); #ifdef HIDE_GAP_GHOSTS if (block->type() == Block::kGap) { @@ -780,7 +777,7 @@ TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, const TrackR return ghost; } -TimelineViewGhostItem* PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode) +TimelineViewGhostItem* PointerTool::AddGhostFromNull(const rational &in, const rational &out, const Track::Reference& track, Timeline::MovementMode mode) { TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); @@ -821,14 +818,14 @@ void PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::Movem } bool PointerTool::IsClipTrimmable(Block *clip, - const QVector& items, + const QVector& items, const Timeline::MovementMode& mode) { - foreach (TimelineViewBlockItem* compare, items) { - if (clip->Track() == compare->Track() + foreach (Block* compare, items) { + if (clip->track() == compare->track() && clip != compare - && ((compare->block()->in() < clip->block()->in() && mode == Timeline::kTrimIn) - || (compare->block()->out() > clip->block()->out() && mode == Timeline::kTrimOut))) { + && ((compare->in() < clip->in() && mode == Timeline::kTrimIn) + || (compare->out() > clip->out() && mode == Timeline::kTrimOut))) { return false; } } @@ -837,7 +834,6 @@ bool PointerTool::IsClipTrimmable(Block *clip, } bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, - const TrackReference& track, Timeline::MovementMode movement, const QVector &selected_items) { @@ -845,13 +841,13 @@ bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, TransitionBlock* transitions[2]; if (movement == Timeline::kMove || movement == Timeline::kTrimOut) { - transitions[0] = TransitionBlock::GetBlockOutTransition(block); + transitions[0] = block->out_transition(); } else { transitions[0] = nullptr; } if (movement == Timeline::kMove || movement == Timeline::kTrimIn) { - transitions[1] = TransitionBlock::GetBlockInTransition(block); + transitions[1] = block->in_transition(); } else { transitions[1] = nullptr; } @@ -865,8 +861,8 @@ bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, bool found = false; - foreach (TimelineViewBlockItem* item, selected_items) { - if (item->block() == transitions[i]) { + foreach (Block* item, selected_items) { + if (item == transitions[i]) { // Do nothing found = true; break; @@ -874,8 +870,7 @@ bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, } if (!found) { - TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(transitions[i], track, - Timeline::kMove); + TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(transitions[i], Timeline::kMove); Q_UNUSED(transition_ghost) diff --git a/app/widget/timelinewidget/tool/pointer.h b/app/widget/timelinewidget/tool/pointer.h index 8c5ef9c33..f85875e65 100644 --- a/app/widget/timelinewidget/tool/pointer.h +++ b/app/widget/timelinewidget/tool/pointer.h @@ -42,9 +42,9 @@ protected: virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode); - TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists = false); + TimelineViewGhostItem* AddGhostFromBlock(Block *block, Timeline::MovementMode mode, bool check_if_exists = false); - TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode); + TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const Track::Reference& track, Timeline::MovementMode mode); /** * @brief Validates Ghosts that are getting their in points trimmed @@ -107,7 +107,7 @@ private: void ProcessGhostsForRolling(); - bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QVector &selected_items); + bool AddMovingTransitionsToClipGhost(Block *block, Timeline::MovementMode movement, const QVector &selected_items); bool movement_allowed_; bool trimming_allowed_; diff --git a/app/widget/timelinewidget/tool/razor.cpp b/app/widget/timelinewidget/tool/razor.cpp index 5c1a78a0a..d406c9ccc 100644 --- a/app/widget/timelinewidget/tool/razor.cpp +++ b/app/widget/timelinewidget/tool/razor.cpp @@ -43,7 +43,7 @@ void RazorTool::MouseMove(TimelineViewMouseEvent *event) } // Split at the current cursor track - TrackReference split_track = event->GetTrack(); + Track::Reference split_track = event->GetTrack(); if (!split_tracks_.contains(split_track)) { split_tracks_.append(split_track); @@ -59,7 +59,7 @@ void RazorTool::MouseRelease(TimelineViewMouseEvent *event) QVector blocks_to_split; - foreach (const TrackReference& track_ref, split_tracks_) { + foreach (const Track::Reference& track_ref, split_tracks_) { Track* track = parent()->GetTrackFromReference(track_ref); if (track == nullptr || track->IsLocked()) { diff --git a/app/widget/timelinewidget/tool/razor.h b/app/widget/timelinewidget/tool/razor.h index ee6da1d49..90976752a 100644 --- a/app/widget/timelinewidget/tool/razor.h +++ b/app/widget/timelinewidget/tool/razor.h @@ -35,7 +35,7 @@ public: virtual void MouseRelease(TimelineViewMouseEvent *event) override; private: - QVector split_tracks_; + QVector split_tracks_; }; } diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index 305a69db9..de3c27339 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -82,20 +82,18 @@ void RippleTool::InitiateDrag(Block *clicked_item, if (block_before_ripple) { TimelineViewGhostItem* ghost; - TrackReference track_ref(track->track_type(), track->Index()); - if (block_before_ripple->type() == Block::kGap) { // If this Block is already a Gap, ghost it now - ghost = AddGhostFromBlock(block_before_ripple, track_ref, trim_mode); + ghost = AddGhostFromBlock(block_before_ripple, trim_mode); } else if (block_before_ripple->next()) { // Assuming this block is NOT at the end of the track (i.e. next != null) // We're going to create a gap after it. If next is a gap, we can just use that if (block_before_ripple->next()->type() == Block::kGap) { - ghost = AddGhostFromBlock(block_before_ripple->next(), track_ref, trim_mode); + ghost = AddGhostFromBlock(block_before_ripple->next(), trim_mode); } else { // If next is NOT a gap, we'll need to create one, for which we'll use a null ghost - ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode); + ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track->ToReference(), trim_mode); ghost->SetData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple)); } } @@ -120,7 +118,7 @@ void RippleTool::FinishDrag(TimelineViewMouseEvent *event) ghost->GetAdjustedLength(), ghost->GetLength()}; - info_list[track->track_type()].append(i); + info_list[track->type()].append(i); } QUndoCommand* command = new QUndoCommand(); diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 082054443..598d7fb8a 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -21,7 +21,6 @@ #include "widget/timelinewidget/timelinewidget.h" #include "node/block/transition/transition.h" -#include "widget/nodeview/nodeviewundo.h" namespace olive { diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index ffb5f185b..b22869d1e 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -24,6 +24,8 @@ #include #include "common/rational.h" +#include "widget/nodeview/nodeviewundo.h" +#include "widget/timelinewidget/timelineundo.h" #include "widget/timelinewidget/view/timelineviewghostitem.h" #include "widget/timelinewidget/view/timelineviewmouseevent.h" @@ -75,7 +77,7 @@ protected: void InsertGapsAtGhostDestination(QUndoCommand* command); - QList snap_points_; + QVector snap_points_; bool dragging_; diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 9b7527e09..57d33d8a6 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -25,6 +25,7 @@ #include "node/factory.h" #include "transition.h" #include "widget/nodeview/nodeviewundo.h" +#include "widget/timelinewidget/timelineundo.h" namespace olive { @@ -35,7 +36,7 @@ TransitionTool::TransitionTool(TimelineWidget *parent) : void TransitionTool::MousePress(TimelineViewMouseEvent *event) { - const TrackReference& track = event->GetTrack(); + const Track::Reference& track = event->GetTrack(); Track* t = parent()->GetTrackFromReference(track); rational cursor_frame = event->GetFrame(); @@ -106,7 +107,7 @@ void TransitionTool::MouseMove(TimelineViewMouseEvent *event) void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) { - const TrackReference& track = ghost_->GetTrack(); + const Track::Reference& track = ghost_->GetTrack(); if (ghost_) { if (!ghost_->GetAdjustedLength().isNull()) { diff --git a/app/widget/timelinewidget/trackview/trackview.cpp b/app/widget/timelinewidget/trackview/trackview.cpp index 318f54e0a..c70478d6b 100644 --- a/app/widget/timelinewidget/trackview/trackview.cpp +++ b/app/widget/timelinewidget/trackview/trackview.cpp @@ -72,7 +72,6 @@ void TrackView::ConnectTrackList(TrackList *list) RemoveTrack(track); } - disconnect(list_, &TrackList::TrackHeightChanged, splitter_, &TrackViewSplitter::SetTrackHeight); disconnect(list_, &TrackList::TrackAdded, this, &TrackView::InsertTrack); disconnect(list_, &TrackList::TrackRemoved, this, &TrackView::RemoveTrack); } @@ -84,7 +83,6 @@ void TrackView::ConnectTrackList(TrackList *list) InsertTrack(track); } - connect(list_, &TrackList::TrackHeightChanged, splitter_, &TrackViewSplitter::SetTrackHeight); connect(list_, &TrackList::TrackAdded, this, &TrackView::InsertTrack); connect(list_, &TrackList::TrackRemoved, this, &TrackView::RemoveTrack); } diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index 6831fe241..db411bcb1 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -111,7 +111,7 @@ void TrackViewItem::LineEditCancelled() void TrackViewItem::UpdateLabel() { if (track_->GetLabel().isEmpty()) { - label_->setText(track_->GetDefaultTrackName(track_->track_type(), track_->Index())); + label_->setText(track_->GetDefaultTrackName(track_->type(), track_->Index())); } else { label_->setText(track_->GetLabel()); } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index a97c702e1..ecf821ad8 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -229,6 +229,13 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect) void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) { + if (!connected_track_list_) { + return; + } + + // Draw block backgrounds + DrawBlocks(painter, false); + // Draw selections if (selections_ && !selections_->isEmpty()) { painter->setPen(Qt::NoPen); @@ -248,6 +255,9 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) } } + // Draw block foregrounds + DrawBlocks(painter, true); + // Draw ghosts if (ghosts_ && !ghosts_->isEmpty()) { painter->setPen(QPen(Qt::yellow, 2)); @@ -268,7 +278,6 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) // Draw beam cursor if (show_beam_cursor_ - && connected_track_list_ && cursor_coord_.GetTrack().type() == connected_track_list_->type()) { painter->setPen(Qt::gray); @@ -328,20 +337,20 @@ Track::Type TimelineView::ConnectedTrackType() return connected_track_list_->type(); } - return Timeline::kTrackTypeNone; + return Track::kNone; } Stream::Type TimelineView::TrackTypeToStreamType(Track::Type track_type) { switch (track_type) { - case Timeline::kTrackTypeNone: - case Timeline::kTrackTypeCount: + case Track::kNone: + case Track::kCount: break; - case Timeline::kTrackTypeVideo: + case Track::kVideo: return Stream::kVideo; - case Timeline::kTrackTypeAudio: + case Track::kAudio: return Stream::kAudio; - case Timeline::kTrackTypeSubtitle: + case Track::kSubtitle: return Stream::kSubtitle; } @@ -355,7 +364,7 @@ TimelineCoordinate TimelineView::ScreenToCoordinate(const QPoint& pt) TimelineCoordinate TimelineView::SceneToCoordinate(const QPointF& pt) { - return TimelineCoordinate(SceneToTime(pt.x()), TrackReference(ConnectedTrackType(), SceneToTrack(pt.y()))); + return TimelineCoordinate(SceneToTime(pt.x()), Track::Reference(ConnectedTrackType(), SceneToTrack(pt.y()))); } TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event) @@ -370,11 +379,53 @@ TimelineViewMouseEvent TimelineView::CreateMouseEvent(const QPoint& pos, Qt::Mou return TimelineViewMouseEvent(scene_pt.x(), GetScale(), timebase(), - TrackReference(ConnectedTrackType(), SceneToTrack(scene_pt.y())), + Track::Reference(ConnectedTrackType(), SceneToTrack(scene_pt.y())), button, modifiers); } +void TimelineView::DrawBlocks(QPainter *painter, bool foreground) +{ + rational start_time = SceneToTime(0); + rational end_time = SceneToTime(viewport()->width()); + + foreach (Track* track, connected_track_list_->GetTracks()) { + // Get first visible block in this track + Block* block = track->NearestBlockBeforeOrAt(start_time); + + while (block) { + if (block->type() == Block::kClip) { + + qreal block_left = qMax(0.0, TimeToScene(block->in())); + qreal block_right = qMin(qreal(viewport()->width()), TimeToScene(block->out())); + + QRectF r(block_left, + GetTrackY(track->Index()), + block_right - block_left, + GetTrackHeight(track->Index())); + + if (foreground) { + painter->setPen(Qt::white); + painter->setBrush(Qt::NoBrush); + painter->drawText(r, block->GetLabel()); + } else { + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(128, 128, 192)); + painter->drawRect(r); + } + + } + + if (block->out() >= end_time) { + // Rest of the clips are offscreen, can break loop now + break; + } + + block = block->next(); + } + } +} + int TimelineView::GetHeightOfAllTracks() const { if (connected_track_list_) { @@ -436,15 +487,7 @@ void TimelineView::SetScrollCoordinates(const QPoint &pt) void TimelineView::ConnectTrackList(TrackList *list) { - if (connected_track_list_) { - disconnect(connected_track_list_, SIGNAL(TrackHeightChanged(int, int)), viewport(), SLOT(update())); - } - connected_track_list_ = list; - - if (connected_track_list_) { - connect(connected_track_list_, SIGNAL(TrackHeightChanged(int, int)), viewport(), SLOT(update())); - } } void TimelineView::SetBeamCursor(const TimelineCoordinate &coord) diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 6f5671663..bccc6c2a7 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -56,7 +56,7 @@ public: void SetBeamCursor(const TimelineCoordinate& coord); - void SetSelectionList(QHash* s) + void SetSelectionList(QHash* s) { selections_ = s; } @@ -66,6 +66,8 @@ public: ghosts_ = ghosts; } + int SceneToTrack(double y); + signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); @@ -107,15 +109,15 @@ private: TimelineViewMouseEvent CreateMouseEvent(QMouseEvent* event); TimelineViewMouseEvent CreateMouseEvent(const QPoint &pos, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); - int GetHeightOfAllTracks() const; + void DrawBlocks(QPainter* painter, bool foreground); - int SceneToTrack(double y); + int GetHeightOfAllTracks() const; void UserSetTime(const int64_t& time); void UpdatePlayheadRect(); - QHash* selections_; + QHash* selections_; QVector* ghosts_; diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.h b/app/widget/timelinewidget/view/timelineviewghostitem.h index 39b9225ca..c7a77de4d 100644 --- a/app/widget/timelinewidget/view/timelineviewghostitem.h +++ b/app/widget/timelinewidget/view/timelineviewghostitem.h @@ -25,7 +25,6 @@ #include "project/item/footage/footage.h" #include "timeline/timelinecommon.h" -#include "timeline/trackreference.h" namespace olive { /** @@ -52,14 +51,14 @@ public: { } - static TimelineViewGhostItem* FromBlock(Block *block, const TrackReference &track) + static TimelineViewGhostItem* FromBlock(Block *block) { TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); ghost->SetIn(block->in()); ghost->SetOut(block->out()); ghost->SetMediaIn(block->media_in()); - ghost->SetTrack(track); + ghost->SetTrack(block->track()->ToReference()); ghost->SetData(kAttachedBlock, Node::PtrToValue(block)); switch (block->type()) { @@ -187,9 +186,9 @@ public: return media_in_ + media_in_adj_; } - TrackReference GetAdjustedTrack() const + Track::Reference GetAdjustedTrack() const { - return TrackReference(track_.type(), track_.index() + track_adj_); + return Track::Reference(track_.type(), track_.index() + track_adj_); } const Timeline::MovementMode& GetMode() const @@ -220,12 +219,12 @@ public: data_.insert(key, value); } - const TrackReference& GetTrack() const + const Track::Reference& GetTrack() const { return track_; } - void SetTrack(const TrackReference& track) + void SetTrack(const Track::Reference& track) { track_ = track; } @@ -258,7 +257,7 @@ private: bool can_have_zero_length_; bool can_move_tracks_; - TrackReference track_; + Track::Reference track_; QHash data_; diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.h b/app/widget/timelinewidget/view/timelineviewmouseevent.h index b1e4bc9ae..d13d62ff5 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.h +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.h @@ -36,7 +36,7 @@ public: TimelineViewMouseEvent(const qreal& scene_x, const double& scale_x, const rational& timebase, - const TrackReference &track, + const Track::Reference &track, const Qt::MouseButton &button, const Qt::KeyboardModifiers& modifiers = Qt::NoModifier) : scene_x_(scene_x), @@ -74,7 +74,7 @@ public: return TimeScaledObject::SceneToTime(scene_x_, scale_x_, timebase_, round); } - const TrackReference& GetTrack() const + const Track::Reference& GetTrack() const { return track_; } @@ -121,7 +121,7 @@ private: double scale_x_; rational timebase_; - TrackReference track_; + Track::Reference track_; Qt::MouseButton button_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index c6244d299..d99c46fbd 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -341,7 +341,7 @@ void MainWindow::ProjectClose(Project *p) // Close any open footage in footage viewer QVector footage = p->get_items_of_type(Item::kFootage); - QList footage_in_viewer = footage_viewer_panel_->GetSelectedFootage(); + QVector footage_in_viewer = footage_viewer_panel_->GetSelectedFootage(); if (!footage_in_viewer.isEmpty()) { // FootageViewer only has the one footage item