timeline: rewrite ripple delete

Fixes #1841
This commit is contained in:
itsmattkc
2022-01-23 23:54:38 -08:00
parent eafa2ca3be
commit 7fa8c6f33a
3 changed files with 135 additions and 38 deletions
+11 -3
View File
@@ -520,20 +520,28 @@ void TimelineWidget::DeleteSelected(bool ripple)
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false));
// Insert ripple command now that it's all cleaned up gaps
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
rational new_playhead = RATIONAL_MAX;
if (ripple) {
TimeRangeList range_list;
QVector<QPair<Track*, TimeRange> > range_list;
foreach (Block* b, blocks_to_delete) {
range_list.insert(TimeRange(b->in(), b->out()));
range_list.append({b->track(), b->range()});
new_playhead = qMin(new_playhead, b->in());
}
command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list));
ripple_command = new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list);
command->add_child(ripple_command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
// Ensures any current drag operations are cancelled
ClearGhosts();
if (ripple_command && ripple_command->HasCommands() && new_playhead != RATIONAL_MAX) {
SetTimeAndSignal(new_playhead);
}
}
void TimelineWidget::IncreaseTrackHeight()
@@ -457,46 +457,128 @@ void TrackListRippleToolCommand::ripple(bool redo)
//
// TimelineRippleDeleteGapsAtRegionsCommand
//
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
{
if (commands_.isEmpty()) {
foreach (const TimeRange& range, regions_) {
rational max_ripple_length = range.length();
int max_gaps = 0;
QHash<Track*, QVector<RemovalRequest> > requested_gaps;
QVector<Block*> blocks_around_range;
// Convert regions to gaps
for (const QPair<Track*, TimeRange> &region : qAsConst(regions_)) {
Track *track = region.first;
const TimeRange &range = region.second;
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());
GapBlock *gap = dynamic_cast<GapBlock*>(track->NearestBlockBeforeOrAt(range.in()));
if (block_at_time) {
if (dynamic_cast<GapBlock*>(block_at_time)) {
max_ripple_length = qMin(block_at_time->length(), max_ripple_length);
} else {
max_ripple_length = 0;
break;
if (gap) {
QVector<RemovalRequest> &gaps_on_track = requested_gaps[track];
RemovalRequest this_req = {gap, range};
// Insertion sort
bool inserted = false;
for (int i=0; i<gaps_on_track.size(); i++) {
if (gaps_on_track.at(i).range.in() < range.in()) {
gaps_on_track.insert(i, this_req);
inserted = true;
break;
}
}
if (!inserted) {
gaps_on_track.append(this_req);
}
max_gaps = qMax(max_gaps, gaps_on_track.size());
}
}
for (int gap_index=0; gap_index<max_gaps; gap_index++) {
rational earliest_point = RATIONAL_MAX;
rational ripple_length = RATIONAL_MAX;
foreach (const QVector<RemovalRequest> &gaps_on_track, requested_gaps) {
if (gap_index < gaps_on_track.size()) {
const RemovalRequest &gap = gaps_on_track.at(gap_index);
earliest_point = qMin(earliest_point, gap.range.in());
ripple_length = qMin(ripple_length, gap.range.length());
}
}
// Determine which gaps will be involved in this operation
QVector<GapBlock*> gaps;
foreach (Track* track, timeline_->GetTracks()) {
if (track->IsLocked()) {
continue;
}
const QVector<RemovalRequest> &requested_gaps_on_track = requested_gaps.value(track);
GapBlock *gap = nullptr;
if (gap_index < requested_gaps_on_track.size()) {
// A requested gap was at this index, use it
gap = requested_gaps_on_track.at(gap_index).gap;
} else {
// No requested gap was at this index, find one
Block *block = track->NearestBlockAfterOrAt(earliest_point);
if (block) {
// Found a block, test if it's a gap
gap = dynamic_cast<GapBlock*>(block);
if (!gap) {
if (block->in() == earliest_point) {
if (block->next()) {
gap = dynamic_cast<GapBlock*>(block->next());
if (!gap) {
ripple_length = 0;
}
}
} else {
gap = dynamic_cast<GapBlock*>(block->previous());
if (!gap) {
ripple_length = 0;
}
}
}
blocks_around_range.append(block_at_time);
} else {
// Assume track finishes here and track won't be affected by this operation
}
}
if (max_ripple_length > 0) {
foreach (Block* resize, blocks_around_range) {
if (resize->length() == max_ripple_length) {
// Remove block entirely
commands_.append(new TrackRippleRemoveBlockCommand(resize->track(), resize));
} else {
// Resize block
commands_.append(new BlockResizeCommand(resize, resize->length() - max_ripple_length));
}
if (gap) {
gaps.append(gap);
ripple_length = qMin(ripple_length, gap->length());
}
if (ripple_length == 0) {
break;
}
}
if (ripple_length > 0) {
foreach (GapBlock *gap, gaps) {
if (gap->length() == ripple_length) {
commands_.append(new TrackRippleRemoveBlockCommand(gap->track(), gap));
} else {
commands_.append(new BlockResizeCommand(gap, gap->length() - ripple_length));
}
}
}
}
}
foreach (UndoCommand* c, commands_) {
c->redo_now();
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
{
for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) {
(*it)->redo_now();
}
}
void TimelineRippleDeleteGapsAtRegionsCommand::undo()
{
for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) {
(*it)->undo_now();
}
}
@@ -202,7 +202,7 @@ private:
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
public:
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const TimeRangeList& regions) :
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector<QPair<Track*, TimeRange> >& regions) :
timeline_(vo),
regions_(regions)
{
@@ -218,22 +218,29 @@ public:
return timeline_->project();
}
bool HasCommands() const
{
return !commands_.isEmpty();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override
{
for (int i=commands_.size()-1;i>=0;i--) {
commands_.at(i)->undo_now();
}
}
virtual void undo() override;
private:
Sequence* timeline_;
TimeRangeList regions_;
QVector<QPair<Track*, TimeRange> > regions_;
QVector<UndoCommand*> commands_;
struct RemovalRequest {
GapBlock *gap;
TimeRange range;
};
};
}