diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 3d2b85417..5c88a4fde 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -150,7 +150,19 @@ Block *TrackOutput::NearestBlockBefore(const rational &time) const return nullptr; } -Block *TrackOutput::NearestBlockAfter(const rational &time) const +Block *TrackOutput::NearestBlockBeforeOrAt(const rational &time) const +{ + foreach (Block* block, block_cache_) { + // 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; + } + } + + return nullptr; +} + +Block *TrackOutput::NearestBlockAfterOrAt(const rational &time) const { foreach (Block* block, block_cache_) { // Blocks are sorted by time, so the first Block after this time is the correct Block @@ -162,6 +174,18 @@ Block *TrackOutput::NearestBlockAfter(const rational &time) const return nullptr; } +Block *TrackOutput::NearestBlockAfter(const rational &time) const +{ + foreach (Block* block, block_cache_) { + // Blocks are sorted by time, so the first Block after this time is the correct Block + if (block->in() > time) { + return block; + } + } + + return nullptr; +} + Block *TrackOutput::BlockAtTime(const rational &time) const { if (IsMuted()) { @@ -288,8 +312,6 @@ void TrackOutput::RippleRemoveBlock(Block *block) void TrackOutput::ReplaceBlock(Block *old, Block *replace) { - Q_ASSERT(old->length() == replace->length()); - BlockInvalidateCache(); int index_of_old_block = block_cache_.indexOf(old); @@ -302,7 +324,11 @@ void TrackOutput::ReplaceBlock(Block *old, Block *replace) UnblockInvalidateCache(); - InvalidateCache(replace->in(), replace->out()); + if (old->length() == replace->length()) { + InvalidateCache(replace->in(), replace->out()); + } else { + InvalidateCache(replace->in(), RATIONAL_MAX); + } } TrackOutput *TrackOutput::TrackFromBlock(Block *block) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index e35d17477..f16f0127d 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -57,8 +57,24 @@ public: Block* BlockContainingTime(const rational& time) const; + /** + * @brief Returns the block that starts BEFORE a given time and ends some time AFTER or precisely AT that time + */ Block* NearestBlockBefore(const rational& time) const; + /** + * @brief Returns the block that starts BEFORE a given time OR the block that starts precisely at that time + */ + Block* NearestBlockBeforeOrAt(const rational& time) const; + + /** + * @brief Returns the block that starts either precisely AT a given time or the soonest block AFTER + */ + Block* NearestBlockAfterOrAt(const rational& time) const; + + /** + * @brief Returns the block that starts AFTER the given time (but never AT the given time) + */ Block* NearestBlockAfter(const rational& time) const; Block* BlockAtTime(const rational& time) const; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 80118431c..95dfdbb7d 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -79,7 +79,12 @@ void TimelinePanel::EditToOut() void TimelinePanel::DeleteSelected() { - static_cast(GetTimeBasedWidget())->DeleteSelected(); + static_cast(GetTimeBasedWidget())->DeleteSelected(false); +} + +void TimelinePanel::RippleDelete() +{ + static_cast(GetTimeBasedWidget())->DeleteSelected(true); } void TimelinePanel::IncreaseTrackHeight() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 784043b23..02c09d1e5 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -51,6 +51,8 @@ public: virtual void DeleteSelected() override; + virtual void RippleDelete() override; + virtual void IncreaseTrackHeight() override; virtual void DecreaseTrackHeight() override; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 31146171a..2cd12b5d6 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -335,8 +335,92 @@ void TimelineWidget::SplitAtPlayhead() } } -void TimelineWidget::DeleteSelectedInternal(const QList blocks, bool transition_aware, bool remove_from_graph, QUndoCommand *command) +void TimelineWidget::DeleteSelectedInternal(QList blocks, + bool transition_aware, + bool remove_from_graph, + bool ripple, + QUndoCommand *command) { + if (ripple) { + for (int i=0;ilength(); + + QList blocks_at_time; + + foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + + // Ignore our track since we've already taking account of the block at this time on our track + if (track == b_track) { + continue; + } + + // 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(b->in()); + + // If we found a block, see what it is + if (block_at_time) { + + // If it's a gap, or we're deleting it (which means it will soon become a gap), it's viable for removing + // or resizing + if (block_at_time->type() == Block::kGap || blocks.contains(block_at_time)) { + + // In an effort to keep all tracks synchronized, we can only ripple a maximum of the smallest gap we find + max_ripple_length = qMin(max_ripple_length, block_at_time->length()); + + } else { + + // If there is no gap here, we cannot ripple at all and must abort this ripple + max_ripple_length = 0; + break; + + } + + blocks_at_time.append(block_at_time); + } + } + + // If we can ripple all the tracks + if (max_ripple_length > 0) { + + // Ripple everything including the main block + blocks_at_time.append(b); + + foreach (Block* resize, blocks_at_time) { + + // If we can remove this whole block, remove the whole block + if (resize->length() == max_ripple_length) { + new TrackRippleRemoveBlockCommand(TrackOutput::TrackFromBlock(resize), resize, command); + + // Also remove this block from our block list so we don't bother replacing it with a gap later + int resize_index = blocks.indexOf(resize); + + if (resize_index >= 0) { + blocks.removeOne(resize); + + // Ensure our iteration remain correct after removing blocks + if (resize_index <= i) { + i--; + } + } + } else { + + // Otherwise, we'll simply shorten the gap/clip + BlockResizeCommand* brc = new BlockResizeCommand(resize, resize->length() - max_ripple_length, command); + + // Perform the resize NOW so that if it's a clip that we're replacing with a gap later, the gap will have + // the correct length + brc->redo(); + + } + } + } + } + } + foreach (Block* b, blocks) { TrackOutput* original_track = TrackOutput::TrackFromBlock(b); @@ -382,7 +466,7 @@ void TimelineWidget::DeleteSelectedInternal(const QList blocks, bool tr } } -void TimelineWidget::DeleteSelected() +void TimelineWidget::DeleteSelected(bool ripple) { QList selected_list = GetSelectedBlocks(); QList blocks_to_delete; @@ -406,7 +490,7 @@ void TimelineWidget::DeleteSelected() QUndoCommand* command = new QUndoCommand(); // Replace blocks with gaps (effectively deleting them) - DeleteSelectedInternal(blocks_to_delete, true, true, command); + DeleteSelectedInternal(blocks_to_delete, true, true, ripple, command); // Clean each track foreach (const TrackReference& track, tracks_affected) { @@ -418,11 +502,6 @@ void TimelineWidget::DeleteSelected() Core::instance()->undo_stack()->pushIfHasChildren(command); } -void TimelineWidget::RippleDelete() -{ - -} - void TimelineWidget::IncreaseTrackHeight() { if (!GetConnectedNode()) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index ca2c99aba..819315456 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -47,9 +47,7 @@ public: void SplitAtPlayhead(); - void DeleteSelected(); - - void RippleDelete(); + void DeleteSelected(bool ripple = false); void IncreaseTrackHeight(); @@ -351,7 +349,7 @@ private: bool dual_transition_; }; - void DeleteSelectedInternal(const QList blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command); + void DeleteSelectedInternal(QList blocks, bool transition_aware, bool remove_from_graph, bool ripple, QUndoCommand* command); void SetBlockLinksSelected(Block *block, bool selected); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 16201c54e..3d21fe7a6 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -216,10 +216,12 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e } } - // If there are any blocks to remove, remove them - parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, false, command); + bool inserting = (event->GetModifiers() & Qt::ControlModifier); - if (event->GetModifiers() & Qt::ControlModifier) { + // If there are any blocks to remove, remove them + parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, false, inserting, command); + + if (inserting) { // Make room to insert clips to InsertGapsAtGhostDestination(parent()->ghost_items_, command); }