/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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
#include "timelineundoripple.h"
#include "timelineundocommon.h"
namespace olive
{
//
// TrackRippleRemoveAreaCommand
//
TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(
Track *track, const TimeRange &range)
: track_(track)
, range_(range)
, allow_splitting_gaps_(false)
, splice_split_command_(nullptr)
{
trim_out_.block = nullptr;
trim_in_.block = nullptr;
}
TrackRippleRemoveAreaCommand::~TrackRippleRemoveAreaCommand()
{
delete splice_split_command_;
qDeleteAll(remove_block_commands_);
}
void TrackRippleRemoveAreaCommand::prepare()
{
// Determine precisely what will be happening to these tracks
Block *first_block = track_->nearest_block_before_or_at(range_.in());
if (!first_block) {
// No blocks at this time, nothing to be done on this track
return;
}
// Determine if this first block is getting trimmed or removed
bool first_block_is_out_trimmed = first_block->in() < range_.in();
bool first_block_is_in_trimmed = first_block->out() > range_.out();
// Set's the block that any insert command should insert AFTER. If the first block is not
// getting out-trimmed, that means first block is either getting removed or in-trimmed, which
// means any insert should happen before it
insert_previous_ = first_block_is_out_trimmed ? first_block :
first_block->previous();
// If it's getting trimmed, determine if it's actually getting spliced
if (first_block_is_out_trimmed && first_block_is_in_trimmed) {
if (!allow_splitting_gaps_ && dynamic_cast(first_block)) {
// As a rule, we don't split gaps, so we just treat it as a trim of the range requested
trim_out_ = { first_block, first_block->length(),
first_block->length() - range_.length() };
} else {
// This block is getting spliced, so we'll handle that later
splice_split_command_ =
new BlockSplitCommand(first_block, range_.in());
}
} else {
// It's just getting trimmed or removed, so we'll append that operation
if (first_block_is_out_trimmed) {
trim_out_ = { first_block, first_block->length(),
first_block->length() -
(first_block->out() - range_.in()) };
} else if (first_block_is_in_trimmed) {
// Block is getting in trimmed
trim_in_ = { first_block, first_block->length(),
first_block->length() -
(range_.out() - first_block->in()) };
} else {
// We know for sure this block is within the range so it will be removed
removals_.append(
RemoveOperation({ first_block, first_block->previous() }));
}
// If the first block is getting in trimmed, we're already at the end of our range
if (!first_block_is_in_trimmed) {
// Loop through the rest of the blocks and determine what to do with those
for (Block *next = first_block->next(); next; next = next->next()) {
bool trimming = (next->out() > range_.out());
if (trimming) {
trim_in_ = { next, next->length(),
next->length() - (range_.out() - next->in()) };
break;
} else {
removals_.append(
RemoveOperation({ next, next->previous() }));
if (next->out() == range_.out()) {
break;
}
}
}
}
}
}
void TrackRippleRemoveAreaCommand::redo()
{
if (splice_split_command_) {
// We're just splicing
splice_split_command_->redo_now();
// Trim the in of the split
Block *split = splice_split_command_->new_block();
split->set_length_and_media_in(split->length() -
(range_.out() - split->in()));
} else {
if (trim_out_.block) {
trim_out_.block->set_length_and_media_out(trim_out_.new_length);
}
if (trim_in_.block) {
trim_in_.block->set_length_and_media_in(trim_in_.new_length);
}
// Perform removals
if (!removals_.isEmpty()) {
foreach (auto op, removals_) {
// Ripple remove them all first
track_->ripple_remove_block(op.block);
}
// Create undo commands for node removals where possible
if (remove_block_commands_.isEmpty()) {
foreach (auto op, removals_) {
if (node_can_be_removed(op.block)) {
remove_block_commands_.append(
create_remove_command(op.block));
}
}
}
foreach (UndoCommand *c, remove_block_commands_) {
c->redo_now();
}
}
}
}
void TrackRippleRemoveAreaCommand::undo()
{
if (splice_split_command_) {
splice_split_command_->undo_now();
} else {
if (trim_out_.block) {
trim_out_.block->set_length_and_media_out(trim_out_.old_length);
}
if (trim_in_.block) {
trim_in_.block->set_length_and_media_in(trim_in_.old_length);
}
// Un-remove any blocks
for (int i = remove_block_commands_.size() - 1; i >= 0; i--) {
remove_block_commands_.at(i)->undo_now();
}
foreach (auto op, removals_) {
track_->insert_block_after(op.block, op.before);
}
}
}
//
// TrackListRippleRemoveAreaCommand
//
void TrackListRippleRemoveAreaCommand::prepare()
{
foreach (Track *track, list_->get_tracks()) {
if (track->is_locked()) {
continue;
}
TrackRippleRemoveAreaCommand *c =
new TrackRippleRemoveAreaCommand(track, range_);
commands_.append(c);
working_tracks_.append(track);
}
}
void TrackListRippleRemoveAreaCommand::redo()
{
foreach (TrackRippleRemoveAreaCommand *c, commands_) {
c->redo_now();
}
}
void TrackListRippleRemoveAreaCommand::undo()
{
foreach (TrackRippleRemoveAreaCommand *c, commands_) {
c->undo_now();
}
}
//
// TimelineRippleRemoveAreaCommand
//
TimelineRippleRemoveAreaCommand::TimelineRippleRemoveAreaCommand(
Sequence *timeline, Rational in, Rational out)
: timeline_(timeline)
{
for (int i = 0; i < Track::k_count; i++) {
add_child(new TrackListRippleRemoveAreaCommand(
timeline->track_list(static_cast(i)), in, out));
}
}
//
// TrackListRippleToolCommand
//
TrackListRippleToolCommand::TrackListRippleToolCommand(
TrackList *track_list, const QHash