/*** Olive - Non-Linear Video Editor Copyright (C) 2019 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 "widget/timelinewidget/timelinewidget.h" #include "node/block/gap/gap.h" #include "widget/nodeview/nodeviewundo.h" TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) : PointerTool(parent) { SetMovementAllowed(false); } void TimelineWidget::RippleTool::MouseReleaseInternal(TimelineViewMouseEvent *event) { Q_UNUSED(event) // For ripple operations, all ghosts will be moving the same way Timeline::MovementMode movement_mode = parent()->ghost_items_.first()->mode(); QUndoCommand* command = new QUndoCommand(); // Find earliest point to ripple around foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); if (b == nullptr) { // This is a gap we are creating // Make sure there's actually a gap being created if (ghost->AdjustedLength() > 0) { GapBlock* gap = new GapBlock(); gap->set_length_and_media_out(ghost->AdjustedLength()); new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), gap, command); Block* block_to_append_gap_to = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kReferenceBlock)); new TrackInsertBlockAfterCommand(parent()->GetTrackFromReference(ghost->Track()), gap, block_to_append_gap_to, command); } } else { // This was a Block that already existed if (ghost->AdjustedLength() > 0) { if (movement_mode == Timeline::kTrimIn) { // We'll need to shift the media in point too new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command); } else { new BlockResizeCommand(b, ghost->AdjustedLength(), command); } } else { // Assumed the Block was a Gap and it was reduced to zero length, remove it here new TrackRippleRemoveBlockCommand(parent()->GetTrackFromReference(ghost->Track()), b, command); new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command); } } } Core::instance()->undo_stack()->pushIfHasChildren(command); } rational TimelineWidget::RippleTool::FrameValidateInternal(rational time_movement, const QVector &ghosts) { // Only validate trimming, and we don't care about "overwriting" since the ripple tool is nondestructive time_movement = ValidateInTrimming(time_movement, ghosts, false); time_movement = ValidateOutTrimming(time_movement, ghosts, false); return time_movement; } void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode, bool allow_gap_trimming) { Q_UNUSED(allow_gap_trimming) PointerTool::InitiateGhosts(clicked_item, trim_mode, true); if (parent()->ghost_items_.isEmpty()) { return; } // Find the earliest ripple rational earliest_ripple = RATIONAL_MAX; foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { rational ghost_ripple_point; if (trim_mode == Timeline::kTrimIn) { ghost_ripple_point = ghost->In(); } else { ghost_ripple_point = ghost->Out(); } earliest_ripple = qMin(earliest_ripple, ghost_ripple_point); } // For each track that does NOT have a ghost, we need to make one for Gaps foreach (TrackOutput* track, parent()->GetConnectedNode()->Tracks()) { // Determine if we've already created a ghost on this track bool ghost_on_this_track_exists = false; foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { if (parent()->GetTrackFromReference(ghost->Track()) == track) { ghost_on_this_track_exists = true; break; } } // If there's no ghost on this track, create one if (!ghost_on_this_track_exists) { // Find the block that starts just before the ripple point, and ends either on or just after it Block* block_before_ripple = track->NearestBlockBefore(earliest_ripple); // If block is null, there will be no blocks after to ripple if (block_before_ripple != nullptr) { 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); } else { // If there's no gap here, we'll need to create one ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode); ghost->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple)); } ghost->SetInvisible(true); } } } }