/*** 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 "timelineundopointer.h" #include "node/block/gap/gap.h" #include "node/block/transition/transition.h" #include "node/project.h" #include "timelineundocommon.h" namespace olive { // // BlockTrimCommand // void BlockTrimCommand::redo() { if (doing_nothing_) { return; } // Determine how much time to invalidate TimeRange invalidate_range; if (mode_ == Timeline::k_trim_in) { invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_); block_->set_length_and_media_in(new_length_); } else { invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_); block_->set_length_and_media_out(new_length_); } if (needs_adjacent_) { if (we_created_adjacent_) { // Add adjacent and insert it adjacent_->setParent(track_->parent()); if (mode_ == Timeline::k_trim_in) { track_->insert_block_before(adjacent_, block_); } else { track_->insert_block_after(adjacent_, block_); } } else if (we_removed_adjacent_) { track_->ripple_remove_block(adjacent_); // It no longer inputs/outputs anything, remove it if (remove_block_from_graph_ && node_can_be_removed(adjacent_)) { if (!deleted_adjacent_command_) { deleted_adjacent_command_ = create_and_run_remove_command(adjacent_); } else { deleted_adjacent_command_->redo_now(); } } } else { Rational adjacent_length = adjacent_->length() + trim_diff_; if (mode_ == Timeline::k_trim_in) { adjacent_->set_length_and_media_out(adjacent_length); } else { adjacent_->set_length_and_media_in(adjacent_length); } } } if (dynamic_cast(block_)) { // Whole transition needs to be invalidated invalidate_range = block_->range(); } } void BlockTrimCommand::undo() { if (doing_nothing_) { return; } // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer if (needs_adjacent_) { if (we_created_adjacent_) { // Adjacent is ours, just delete it track_->ripple_remove_block(adjacent_); adjacent_->setParent(&memory_manager_); } else { if (we_removed_adjacent_) { if (deleted_adjacent_command_) { // We deleted adjacent, restore it now deleted_adjacent_command_->undo_now(); } if (mode_ == Timeline::k_trim_in) { track_->insert_block_before(adjacent_, block_); } else { track_->insert_block_after(adjacent_, block_); } } else { Rational adjacent_length = adjacent_->length() - trim_diff_; if (mode_ == Timeline::k_trim_in) { adjacent_->set_length_and_media_out(adjacent_length); } else { adjacent_->set_length_and_media_in(adjacent_length); } } } } TimeRange invalidate_range; if (mode_ == Timeline::k_trim_in) { block_->set_length_and_media_in(old_length_); invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_); } else { block_->set_length_and_media_out(old_length_); invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_); } if (dynamic_cast(block_)) { // Whole transition needs to be invalidated invalidate_range = block_->range(); } } void BlockTrimCommand::prepare() { // Store old length old_length_ = block_->length(); // Determine if the length isn't changing, in which case we set a flag to do nothing if ((doing_nothing_ = (old_length_ == new_length_))) { return; } // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer trim_diff_ = old_length_ - new_length_; // Retrieve our adjacent block (or nullptr if none) if (mode_ == Timeline::k_trim_in) { adjacent_ = block_->previous(); } else { adjacent_ = block_->next(); } // Ignore when trimming the out with no adjacent, because the user must have trimmed the end // of the last block in the track, so we don't need to do anything elses needs_adjacent_ = (mode_ == Timeline::k_trim_in || adjacent_); if (needs_adjacent_) { // If we're trimming shorter, we need an adjacent, so check if we have a viable one. we_created_adjacent_ = (trim_diff_ > 0 && (!adjacent_ || (!dynamic_cast(adjacent_) && !trim_is_a_roll_edit_))); if (we_created_adjacent_) { // We shortened but don't have a viable adjacent to lengthen, so we create one adjacent_ = new GapBlock(); adjacent_->set_length_and_media_out(trim_diff_); } else { // Determine if we're removing the adjacent Rational adjacent_length = adjacent_->length() + trim_diff_; we_removed_adjacent_ = adjacent_length.isNull(); } } } // // TrackSlideCommand // void TrackSlideCommand::redo() { // Make sure all movement blocks' old positions are invalidated TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out()); // We will always have an in adjacent if there was a valid slide if (we_created_in_adjacent_) { // We created in adjacent, so all we have to do is insert it in_adjacent_->setParent(track_->parent()); track_->insert_block_before(in_adjacent_, blocks_.first()); } else if (-movement_ == in_adjacent_->length()) { // Movement will remove in adjacent track_->ripple_remove_block(in_adjacent_); if (node_can_be_removed(in_adjacent_)) { if (!in_adjacent_remove_command_) { in_adjacent_remove_command_ = create_remove_command(in_adjacent_); } in_adjacent_remove_command_->redo_now(); } we_removed_in_adjacent_ = true; } else { // Simply resize adjacent in_adjacent_->set_length_and_media_out(in_adjacent_->length() + movement_); } // We may not have an out adjacent if the slide was at the end of the track if (out_adjacent_) { if (we_created_out_adjacent_) { // We created out adjacent, so we just have to insert it out_adjacent_->setParent(track_->parent()); track_->insert_block_after(out_adjacent_, blocks_.last()); } else if (movement_ == out_adjacent_->length()) { // Movement will remove out adjacent track_->ripple_remove_block(out_adjacent_); if (node_can_be_removed(out_adjacent_)) { if (!out_adjacent_remove_command_) { out_adjacent_remove_command_ = create_remove_command(out_adjacent_); } out_adjacent_remove_command_->redo_now(); } we_removed_out_adjacent_ = true; } else { // Simply resize adjacent out_adjacent_->set_length_and_media_in(out_adjacent_->length() - movement_); } } // 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())); } void TrackSlideCommand::undo() { // Make sure all movement blocks' old positions are invalidated TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out()); if (we_created_in_adjacent_) { // We created this, so we can remove it now track_->ripple_remove_block(in_adjacent_); in_adjacent_->setParent(&memory_manager_); } else if (we_removed_in_adjacent_) { if (in_adjacent_remove_command_) { // We removed this, so we can restore it now in_adjacent_remove_command_->undo_now(); } track_->insert_block_before(in_adjacent_, blocks_.first()); } else { // Simply resize adjacent in_adjacent_->set_length_and_media_out(in_adjacent_->length() - movement_); } if (out_adjacent_) { if (we_created_out_adjacent_) { // We created this, so we can remove it now track_->ripple_remove_block(out_adjacent_); out_adjacent_->setParent(&memory_manager_); } else if (we_removed_out_adjacent_) { if (out_adjacent_remove_command_) { out_adjacent_remove_command_->undo_now(); } track_->insert_block_after(out_adjacent_, blocks_.last()); } else { out_adjacent_->set_length_and_media_in(out_adjacent_->length() + movement_); } } // 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())); } void TrackSlideCommand::prepare() { if (!in_adjacent_) { in_adjacent_ = new GapBlock(); in_adjacent_->set_length_and_media_out(movement_); in_adjacent_->setParent(&memory_manager_); we_created_in_adjacent_ = true; } else { we_created_in_adjacent_ = false; } if (!out_adjacent_ && blocks_.last()->next()) { out_adjacent_ = new GapBlock(); out_adjacent_->set_length_and_media_out(-movement_); out_adjacent_->setParent(&memory_manager_); we_created_out_adjacent_ = true; } else { we_created_out_adjacent_ = false; } } // // TrackPlaceBlockCommand // TrackPlaceBlockCommand::~TrackPlaceBlockCommand() { delete ripple_remove_command_; qDeleteAll(add_track_commands_); } void TrackPlaceBlockCommand::redo() { // Determine if we need to add tracks if (track_index_ >= timeline_->get_tracks().size()) { if (add_track_commands_.isEmpty()) { // First redo, create tracks now add_track_commands_.resize(track_index_ - timeline_->get_tracks().size() + 1); for (int i = 0; i < add_track_commands_.size(); i++) { add_track_commands_[i] = new TimelineAddTrackCommand(timeline_); } } for (int i = 0; i < add_track_commands_.size(); i++) { add_track_commands_.at(i)->redo_now(); } } Track *track = timeline_->get_track_at(track_index_); bool append = (in_ >= track->track_length()); // Check if the placement location is past the end of the timeline if (append) { if (in_ > track->track_length()) { // If so, insert a gap here if (!gap_) { gap_ = new GapBlock(); gap_->set_length_and_media_out(in_ - track->track_length()); } gap_->setParent(track->parent()); track->append_block(gap_); } track->append_block(insert_); } else { // Place the Block at this point if (!ripple_remove_command_) { ripple_remove_command_ = new TrackRippleRemoveAreaCommand( track, TimeRange(in_, in_ + insert_->length())); ripple_remove_command_->set_allow_splitting_gaps(true); } ripple_remove_command_->redo_now(); track->insert_block_after(insert_, ripple_remove_command_->get_insertion_index()); } } void TrackPlaceBlockCommand::undo() { Track *t = timeline_->get_track_at(track_index_); TimeRange insert_range(insert_->in(), insert_->out()); // Firstly, remove our insert t->ripple_remove_block(insert_); if (ripple_remove_command_) { // If we ripple removed, just undo that ripple_remove_command_->undo_now(); } else if (gap_) { t->ripple_remove_block(gap_); gap_->setParent(&memory_manager_); } // Remove tracks if we added them for (int i = add_track_commands_.size() - 1; i >= 0; i--) { add_track_commands_.at(i)->undo_now(); } } }