diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 0fb644e94..3b339f536 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -256,6 +256,39 @@ NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const return table; } +TransitionBlock *GetBlockTransitionInternal(Block *block, Timeline::MovementMode mode) +{ + // See if this block outputs to a transition + foreach (NodeEdgePtr edge, block->output()->edges()) { + Node* connected_node = edge->input()->parentNode(); + + if (connected_node->IsBlock()) { + Block* connected_block = static_cast(connected_node); + + if (connected_block->type() == Block::kTransition) { + TransitionBlock* connected_transition = static_cast(connected_block); + + if ((mode == Timeline::kTrimIn && edge->input() == connected_transition->in_block_input()) + || (mode == Timeline::kTrimOut && edge->input() == connected_transition->out_block_input())) { + return connected_transition; + } + } + } + } + + return nullptr; +} + +TransitionBlock *TransitionBlock::GetBlockInTransition(Block *block) +{ + return GetBlockTransitionInternal(block, Timeline::kTrimIn); +} + +TransitionBlock *TransitionBlock::GetBlockOutTransition(Block *block) +{ + return GetBlockTransitionInternal(block, Timeline::kTrimOut); +} + void TransitionBlock::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const { Q_UNUSED(value) diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 34a6f074d..d8c5c6a39 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -51,6 +51,10 @@ public: virtual NodeValueTable Value(NodeValueDatabase &value) const override; + static TransitionBlock* GetBlockInTransition(Block* block); + + static TransitionBlock* GetBlockOutTransition(Block* block); + protected: virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const; diff --git a/app/node/node.cpp b/app/node/node.cpp index 051575c05..ec38c7fdb 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -556,7 +556,7 @@ bool Node::OutputsTo(const QString &id, bool recursively) const return false; } -bool Node::OutputsTo(NodeInput *input, bool recursively) const +bool Node::OutputsTo(NodeInput *input, bool recursively, bool include_arrays) const { QList outputs = GetOutputs(); @@ -566,7 +566,10 @@ bool Node::OutputsTo(NodeInput *input, bool recursively) const if (connected == input) { return true; - } else if (recursively && connected->parentNode()->OutputsTo(input, recursively)) { + } else if (include_arrays && input->IsArray() + && static_cast(input)->sub_params().contains(connected)) { + return true; + } else if (recursively && connected->parentNode()->OutputsTo(input, recursively, include_arrays)) { return true; } } diff --git a/app/node/node.h b/app/node/node.h index 9260ed44f..ba05359ce 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -224,7 +224,7 @@ public: /** * @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node. */ - bool OutputsTo(NodeInput* input, bool recursively) const; + bool OutputsTo(NodeInput* input, bool recursively, bool include_arrays) const; /** * @brief Returns whether this node ever receives an input from a particular node instance diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index e8ac028bd..63163da0e 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -472,67 +472,13 @@ void TimelineWidget::SplitAtPlayhead() } } -void TimelineWidget::DeleteSelectedInternal(const QList &blocks, - bool transition_aware, +void TimelineWidget::ReplaceBlocksWithGaps(const QList &blocks, bool remove_from_graph, QUndoCommand *command) { foreach (Block* b, blocks) { TrackOutput* original_track = TrackOutput::TrackFromBlock(b); - /*if (transition_aware && b->type() == Block::kTransition) { - // Deleting transitions restores their in/out offsets to their attached blocks - TransitionBlock* transition = static_cast(b); - - // Ripple remove transition - new TrackRippleRemoveBlockCommand(original_track, - transition, - command); - - // Resize attached blocks to make up length - if (transition->connected_in_block()) { - new BlockResizeWithMediaInCommand(transition->connected_in_block(), - transition->connected_in_block()->length() + transition->in_offset(), - command); - } - - if (transition->connected_out_block()) { - new BlockResizeCommand(transition->connected_out_block(), - transition->connected_out_block()->length() + transition->out_offset(), - command); - } - } else */ - - - /* - if (b->next()) { - - new TrackRippleRemoveBlockCommand(original_track, b, command); - - if (b->previous() && b->previous()->type() == Block::kGap - && b->next() && b->next()->type() == Block::kGap) { - - // Both previous AND next are blocks. We'll want to merge them together. - new TrackRippleRemoveBlockCommand(original_track, b->next(), command); - - } else { - - // Make new gap and replace old Block with it for now - GapBlock* gap = new GapBlock(); - gap->set_length_and_media_out(b->length()); - - new NodeAddCommand(static_cast(b->parent()), - gap, - command); - - new TrackReplaceBlockCommand(original_track, - b, - gap, - command); - } - } - */ - new TrackReplaceBlockWithGapCommand(original_track, b, command); if (remove_from_graph) { @@ -566,17 +512,30 @@ void TimelineWidget::DeleteSelected(bool ripple) QUndoCommand* command = new QUndoCommand(); - // Replace blocks with gaps (effectively deleting them) - DeleteSelectedInternal(blocks_to_delete, true, true, command); + QList clips_to_delete; + QList transitions_to_delete; - /* - // Clean each track - foreach (const TrackReference& track, tracks_affected) { - new TrackCleanGapsCommand(GetConnectedNode()->track_list(track.type()), - track.index(), - command); + foreach (Block* b, blocks_to_delete) { + if (b->type() == Block::kClip) { + clips_to_delete.append(b); + } else if (b->type() == Block::kTransition) { + transitions_to_delete.append(static_cast(b)); + } + } + + // Replace clips with gaps (effectively deleting them) + ReplaceBlocksWithGaps(clips_to_delete, true, command); + + // For transitions, remove them but extend their attached blocks to fill their place + foreach (TransitionBlock* transition, transitions_to_delete) { + new TransitionRemoveCommand(TrackOutput::TrackFromBlock(transition), + transition, + command); + + new NodeRemoveWithExclusiveDeps(static_cast(GetConnectedNode()->parent()), + transition, + command); } - */ // Insert ripple command now that it's all cleaned up gaps if (ripple) { @@ -634,12 +593,16 @@ void TimelineWidget::ToggleLinksOnSelected() { QList sel = GetSelectedBlocks(); - // Prioritize unlinking - QList blocks; bool link = true; foreach (TimelineViewBlockItem* item, sel) { + // Only clips can be linked + if (item->block()->type() != Block::kClip) { + continue; + } + + // Prioritize unlinking, if any block has links, assume we're unlinking if (link && item->block()->HasLinks()) { link = false; } diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index fbd76a47f..c3d6d5912 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -26,6 +26,7 @@ #include #include "core.h" +#include "node/block/transition/transition.h" #include "node/output/viewer/viewer.h" #include "snapservice.h" #include "timeline/timelinecommon.h" @@ -189,18 +190,18 @@ private: * Validation is the process of ensuring that whatever movements the user is making are "valid" and "legal". This * function's validation ensures that no Ghost's in point ends up in a negative timecode. */ - rational ValidateTimeMovement(rational movement, const QVector ghosts); + rational ValidateTimeMovement(rational movement); /** * @brief Validates Ghosts that are moving vertically (track-based) * * This function's validation ensures that no Ghost's track ends up in a negative (non-existent) track. */ - int ValidateTrackMovement(int movement, const QVector ghosts); + int ValidateTrackMovement(int movement, const QVector &ghosts); - void GetGhostData(const QVector& ghosts, rational *earliest_point, rational *latest_point); + void GetGhostData(rational *earliest_point, rational *latest_point); - void InsertGapsAtGhostDestination(const QVector& ghosts, QUndoCommand* command); + void InsertGapsAtGhostDestination(QUndoCommand* command); QList snap_points_; @@ -242,9 +243,9 @@ private: virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, Timeline::MovementMode trim_mode); - TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode); + TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool trim_overwrite_allowed); - TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode); + TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode, bool trim_overwrite_allowed); /** * @brief Validates Ghosts that are getting their in points trimmed @@ -252,7 +253,7 @@ private: * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no * Ghost's length becomes 0 or negative. */ - rational ValidateInTrimming(rational movement, const QVector ghosts, bool prevent_overwriting); + rational ValidateInTrimming(rational movement); /** * @brief Validates Ghosts that are getting their out points trimmed @@ -260,10 +261,21 @@ private: * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no * Ghost's length becomes 0 or negative. */ - rational ValidateOutTrimming(rational movement, const QVector ghosts, bool prevent_overwriting); + rational ValidateOutTrimming(rational movement); virtual void ProcessDrag(const TimelineCoordinate &mouse_pos); + enum GhostMode { + kPointer, + kRolling, + kSlide + }; + + void InitiateDragInternal(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode, + GhostMode pointer_mode, + bool trim_overwrite_allowed); + const Timeline::MovementMode& drag_movement_mode() const { return drag_movement_mode_; @@ -284,11 +296,6 @@ private: track_movement_allowed_ = e; } - void SetTrimOverwriteAllowed(bool e) - { - trim_overwrite_allowed_ = e; - } - void SetGapTrimmingAllowed(bool e) { gap_trimming_allowed_ = e; @@ -297,16 +304,21 @@ private: private: Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x); - void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode); + void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode, bool trim_overwrite_allowed); bool IsClipTrimmable(TimelineViewBlockItem* clip, const QList& items, const Timeline::MovementMode& mode); + void ProcessGhostsForSliding(); + + void ProcessGhostsForRolling(); + + bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QList &selected_items); + bool movement_allowed_; bool trimming_allowed_; bool track_movement_allowed_; - bool trim_overwrite_allowed_; bool gap_trimming_allowed_; bool rubberband_selecting_; @@ -383,8 +395,6 @@ private: RollingTool(TimelineWidget* parent); protected: - virtual void FinishDrag(TimelineViewMouseEvent *event) override; - virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, Timeline::MovementMode trim_mode) override; }; @@ -395,7 +405,6 @@ private: SlideTool(TimelineWidget* parent); protected: - virtual void FinishDrag(TimelineViewMouseEvent *event) override; virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, Timeline::MovementMode trim_mode) override; @@ -455,7 +464,7 @@ private: void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command); - void DeleteSelectedInternal(const QList& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command); + void ReplaceBlocksWithGaps(const QList& blocks, bool remove_from_graph, QUndoCommand* command); void SetBlockLinksSelected(Block *block, bool selected); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 8a409f0ab..9850a6eab 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -123,14 +123,14 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event) rational time_movement = event->GetFrame() - drag_start_.GetFrame(); int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index(); - time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); + time_movement = ValidateTimeMovement(time_movement); track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); // If snapping is enabled, check for snap points if (Core::instance()->snapping()) { parent()->SnapPoint(snap_points_, &time_movement); - time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); + time_movement = ValidateTimeMovement(time_movement); track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); } @@ -398,7 +398,7 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert) // Check if we're inserting if (insert) { - InsertGapsAtGhostDestination(parent()->ghost_items_, command); + InsertGapsAtGhostDestination(command); } for (int i=0;ighost_items_.size();i++) { diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 6afac6c25..9c17f4e8d 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -41,7 +41,6 @@ TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) : movement_allowed_(true), trimming_allowed_(true), track_movement_allowed_(true), - trim_overwrite_allowed_(false), gap_trimming_allowed_(false), rubberband_selecting_(false) { @@ -119,31 +118,31 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event) if (rubberband_selecting_) { // Process rubberband select parent()->MoveRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier)); - return; - } + } else { + // Process drag + if (!dragging_) { - if (!dragging_) { + // Now that the cursor has moved, we will assume the intention is to drag - // Now that the cursor has moved, we will assume the intention is to drag + // Clear snap points + snap_points_.clear(); - // Clear snap points - snap_points_.clear(); + // If we're performing an action, we can initiate ghosts + if (drag_movement_mode_ != Timeline::kNone) { + InitiateDrag(clicked_item_, drag_movement_mode_); + } + + // Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed + dragging_ = true; - // If we're performing an action, we can initiate ghosts - if (drag_movement_mode_ != Timeline::kNone) { - InitiateDrag(clicked_item_, drag_movement_mode_); } - // Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed - dragging_ = true; + if (dragging_ && !parent()->ghost_items_.isEmpty()) { - } - - if (dragging_ && !parent()->ghost_items_.isEmpty()) { - - // We're already dragging AND we have ghosts to work with - ProcessDrag(event->GetCoordinates()); + // We're already dragging AND we have ghosts to work with + ProcessDrag(event->GetCoordinates()); + } } } @@ -157,10 +156,12 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event) } if (dragging_) { + // If we were dragging, process the end of the drag if (!parent()->ghost_items_.isEmpty()) { FinishDrag(event); } + // Clean up parent()->ClearGhosts(); snap_points_.clear(); @@ -193,92 +194,141 @@ void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event) } } -void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) +void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item, + Timeline::MovementMode trim_mode, + GhostMode pointer_mode, + bool trim_overwrite_allowed) { - QList ghosts_moving; - QList blocks_moving; - QList ghosts_trimming; - QList blocks_trimming; + // Get list of selected blocks + QList clips = parent()->GetSelectedBlocks(); - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (!ghost->HasBeenAdjusted()) { - continue; + if (trim_mode == Timeline::kMove) { + + // Each block type has different behavior, so we determine the type of the block that was + // clicked and filter out any others. + Block::Type clicked_block_type = clicked_item->block()->type(); + + // Gaps are not allowed to move, and since we only allow moving one block type at a time, + // dragging a gap is a no-op + if (clicked_block_type == Block::kGap) { + return; } - Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); + // Create ghosts for moving + foreach (TimelineViewBlockItem* clip_item, clips) { + Block* block = clip_item->block(); - if (ghost->mode() == Timeline::kMove) { - ghosts_moving.append(ghost); - blocks_moving.append(b); - } else if (Timeline::IsATrimMode(ghost->mode())) { - ghosts_trimming.append(ghost); - blocks_trimming.append(b); - } - } - - if (blocks_moving.isEmpty() && blocks_trimming.isEmpty()) { - // Likely means no block was adjusted, so we can skip the rest of the processing - return; - } - - // See if we're duplicated because ALT is held (only moved blocks can duplicate) - bool duplicate_clips = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::AltModifier); - bool inserting = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::ControlModifier); - - QUndoCommand* command = new QUndoCommand(); - - for (int i=0;iGetTrackFromReference(ghost->GetAdjustedTrack()), - blocks_trimming.at(i), - ghost->AdjustedLength(), - ghost->mode(), - command); - } - - if (!blocks_moving.isEmpty()) { - // If we're not duplicating, "remove" the clips and replace them with gaps - if (!duplicate_clips) { - parent()->DeleteSelectedInternal(blocks_moving, false, false, command); - } - - if (inserting) { - // If we're inserting, ripple everything at the destination with gaps - InsertGapsAtGhostDestination(parent()->ghost_items_, command); - } - - // Now we can re-add each clip - for (int i=0;icopy(); - - new NodeAddCommand(static_cast(block->parent()), - copy, - command); - - new NodeCopyInputsCommand(block, copy, true, command); - - // Place the copy instead of the original block - block = static_cast(copy); + if (block->type() == Block::kGap) { + // Gaps cannot move, ignore this block + continue; } - const TrackReference& track_ref = ghost->GetAdjustedTrack(); - new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()), - track_ref.index(), - block, - ghost->GetAdjustedIn(), - command); + if (clicked_block_type == Block::kTransition && block->type() != Block::kTransition) { + // Transitions always slide rather than move, so if we clicked a transition, ignore any + // non-transitions + continue; + } + + // Create ghost + TimelineViewGhostItem* ghost = AddGhostFromBlock(block, clip_item->Track(), + trim_mode, trim_overwrite_allowed); + + if (clicked_block_type == Block::kTransition) { + // Transition moves are always a slide + ghost->setData(TimelineViewGhostItem::kPointerToolMode, kSlide); + } else { + // Set to default behavior + ghost->setData(TimelineViewGhostItem::kPointerToolMode, pointer_mode); + + // Include transitions (if any) + AddMovingTransitionsToClipGhost(block, clip_item->Track(), trim_mode, clips); + } } - // FIXME: Heavy optimization since MOST of the timeline does NOT change in this time - } + // If we slid the blocks, we must process them as such + if (clicked_block_type == Block::kTransition || pointer_mode == kSlide) { + ProcessGhostsForSliding(); + } - Core::instance()->undo_stack()->pushIfHasChildren(command); + } else { + + // "Multi-trim" is trimming a clip on more than one track. Only the earliest (for in trimming) + // or latest (for out trimming) clip on each track can be trimmed. Therefore, it's only enabled + // if the clicked item is the earliest/latest on its track. + bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode); + + // Create ghosts for trimming + foreach (TimelineViewBlockItem* clip_item, clips) { + if (clip_item != clicked_item + && (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) { + // Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We + // won't include it. + continue; + } + + Block* block = clip_item->block(); + Timeline::MovementMode block_mode = trim_mode; + bool block_trim_overwrite_allowed = trim_overwrite_allowed; + + // Some tools interpret "gap trimming" as equivalent to resizing the adjacent block. In that + // scenario, we include the adjacent block instead. + if (block->type() == Block::kGap && !gap_trimming_allowed_) { + block = (trim_mode == Timeline::kTrimIn) ? block->previous() : block->next(); + block_mode = FlipTrimMode(trim_mode); + + // If there's no adjacent block, do nothing here + if (!block) { + continue; + } + } + + // For transitions, we create a rolling edit with the attached clip + if (block->type() == Block::kTransition) { + TransitionBlock* transition = static_cast(block); + TimelineViewGhostItem* g = nullptr; + + Block* previous = transition->previous(); + Block* next = transition->next(); + + if (block_mode == Timeline::kTrimIn + && previous + && (previous == transition->connected_out_block() || previous->type() == Block::kGap)) { + g = AddGhostFromBlock(previous, clip_item->Track(), Timeline::kTrimOut, true); + } else if (block_mode == Timeline::kTrimOut + && next + && (next == transition->connected_in_block() || next->type() == Block::kGap)) { + g = AddGhostFromBlock(next, clip_item->Track(), Timeline::kTrimIn, true); + } + + if (g) { + g->setData(TimelineViewGhostItem::kPointerToolMode, kRolling); + block_trim_overwrite_allowed = true; + } + } + + // Create ghost for this block + TimelineViewGhostItem* ghost = AddGhostFromBlock(block, clip_item->Track(), block_mode, block_trim_overwrite_allowed); + + if (block->type() == Block::kTransition) { + + // If this is a transition, set to rolling as above + ghost->setData(TimelineViewGhostItem::kPointerToolMode, kRolling); + + } else { + + // For trimmed clips, we also "move" the transitions if any are attached + if (AddMovingTransitionsToClipGhost(block, clip_item->Track(), trim_mode, clips)) { + ghost->setData(TimelineViewGhostItem::kPointerToolMode, kSlide); + } else { + ghost->setData(TimelineViewGhostItem::kPointerToolMode, kPointer); + } + } + } + + if (pointer_mode == kRolling) { + ProcessGhostsForRolling(); + } + } } void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) @@ -292,17 +342,17 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po rational time_movement = mouse_pos.GetFrame() - drag_start_.GetFrame(); // Validate movement (enforce all ghosts moving in legal ways) - time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); - time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); - time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); + time_movement = ValidateTimeMovement(time_movement); + time_movement = ValidateInTrimming(time_movement); + time_movement = ValidateOutTrimming(time_movement); // Perform snapping if enabled (adjusts time_movement if it's close to any potential snap points) if (Core::instance()->snapping()) { parent()->SnapPoint(snap_points_, &time_movement); - time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); - time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); - time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); + time_movement = ValidateTimeMovement(time_movement); + time_movement = ValidateInTrimming(time_movement); + time_movement = ValidateOutTrimming(time_movement); } // Validate ghosts that are being moved (clips from other track types do NOT get moved) @@ -357,6 +407,140 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po parent()); } +struct GhostBlockPair { + TimelineViewGhostItem* ghost; + Block* block; +}; + +void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) +{ + QList blocks_moving; + QList blocks_sliding; + QList blocks_trimming; + + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + if (ghost->HasBeenAdjusted()) { + Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); + + if (ghost->mode() == Timeline::kMove) { + if (ghost->data(TimelineViewGhostItem::kPointerToolMode) == kSlide) { + blocks_sliding.append({ghost, b}); + } else { + blocks_moving.append({ghost, b}); + } + } else if (Timeline::IsATrimMode(ghost->mode())) { + blocks_trimming.append({ghost, b}); + } + } + } + + if (blocks_moving.isEmpty() + && blocks_trimming.isEmpty() + && blocks_sliding.isEmpty()) { + // No blocks were adjusted, so nothing to do + return; + } + + // See if we're duplicated because ALT is held (only moved blocks can duplicate) + bool duplicate_clips = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::AltModifier); + bool inserting = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::ControlModifier); + + // Slide info + QVector slide_info; + + QUndoCommand* command = new QUndoCommand(); + + foreach (const GhostBlockPair& p, blocks_trimming) { + TimelineViewGhostItem* ghost = p.ghost; + + GhostMode m = static_cast(ghost->data(TimelineViewGhostItem::kPointerToolMode).toInt()); + + switch (m) { + case kPointer: + case kRolling: + if (m != kRolling || ghost->mode() == drag_movement_mode()) { + BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()), + p.block, + ghost->AdjustedLength(), + ghost->mode(), + command); + + if (m == kRolling) { + c->SetAllowNonGapTrimming(true); + } + } + break; + case kSlide: + slide_info.append({parent()->GetTrackFromReference(ghost->Track()), + p.block, + ghost->mode(), + ghost->AdjustedLength(), + ghost->Length()}); + break; + } + } + + if (!blocks_moving.isEmpty()) { + // If we're not duplicating, "remove" the clips and replace them with gaps + if (!duplicate_clips) { + QList blocks_to_delete; + + foreach (const GhostBlockPair& p, blocks_moving) { + blocks_to_delete.append(p.block); + } + + parent()->ReplaceBlocksWithGaps(blocks_to_delete, false, command); + } + + if (inserting) { + // If we're inserting, ripple everything at the destination with gaps + InsertGapsAtGhostDestination(command); + } + + // Now we can re-add each clip + foreach (const GhostBlockPair& p, blocks_moving) { + Block* block = p.block; + + if (duplicate_clips) { + // Duplicate rather than move + Node* copy = block->copy(); + + new NodeAddCommand(static_cast(block->parent()), + copy, + command); + + new NodeCopyInputsCommand(block, copy, true, command); + + // Place the copy instead of the original block + block = static_cast(copy); + } + + const TrackReference& track_ref = p.ghost->GetAdjustedTrack(); + new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()), + track_ref.index(), + block, + p.ghost->GetAdjustedIn(), + command); + } + } + + if (!blocks_sliding.isEmpty()) { + foreach (const GhostBlockPair& p, blocks_sliding) { + slide_info.append({parent()->GetTrackFromReference(p.ghost->Track()), + p.block, + p.ghost->mode(), + p.ghost->GetAdjustedIn(), + p.ghost->In()}); + } + } + + if (!slide_info.isEmpty()) { + new TrackSlideCommand(slide_info, command); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x) { double kTrimHandle = QFontMetricsWidth(parent()->fontMetrics(), "H"); @@ -378,73 +562,22 @@ Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(Timelin void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item, Timeline::MovementMode trim_mode) { - // Get list of selected blocks - QList clips = parent()->GetSelectedBlocks(); - - if (trim_mode == Timeline::kMove) { - - // Create ghosts for moving - foreach (TimelineViewBlockItem* clip_item, clips) { - - // Gaps are not allowed to move, so we ignore those here - if (clip_item->block()->type() == Block::kGap) { - continue; - } - - AddGhostFromBlock(clip_item->block(), clip_item->Track(), trim_mode); - } - - } else { - - // "Multi-trim" is trimming a clip on more than one track. Only the earliest (for in trimming) - // or latest (for out trimming) clip on each track can be trimmed. Therefore, it's only enabled - // if the clicked item is the earliest/latest on its track. - bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode); - - // Create ghosts for trimming - foreach (TimelineViewBlockItem* clip_item, clips) { - if (clip_item != clicked_item - && (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) { - // Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We - // won't include it. - continue; - } - - Block* block = clip_item->block(); - Timeline::MovementMode block_mode = trim_mode; - - // Some tools interpret "gap trimming" as equivalent to resizing the adjacent block. In that - // scenario, we include the adjacent block instead. - if (block->type() == Block::kGap && !gap_trimming_allowed_) { - block = (trim_mode == Timeline::kTrimIn) ? block->previous() : block->next(); - block_mode = FlipTrimMode(trim_mode); - - // If there's no adjacent block, do nothing here - if (!block) { - continue; - } - } - - // Create ghost for this block - AddGhostFromBlock(block, clip_item->Track(), block_mode); - } - - } + InitiateDragInternal(clicked_item, trim_mode, kPointer, false); } -TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode) +TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool trim_overwrite_allowed) { TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block, track, parent()->GetTrackY(track), parent()->GetTrackHeight(track)); - AddGhostInternal(ghost, mode); + AddGhostInternal(ghost, mode, trim_overwrite_allowed); return ghost; } -TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode) +TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode, bool trim_overwrite_allowed) { TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); @@ -453,14 +586,15 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const ratio ghost->SetTrack(track); ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track)); - AddGhostInternal(ghost, mode); + AddGhostInternal(ghost, mode, trim_overwrite_allowed); return ghost; } -void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode) +void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode, bool trim_overwrite_allowed) { ghost->SetMode(mode); + ghost->setData(TimelineViewGhostItem::kTrimOverwriteAllowed, trim_overwrite_allowed); // Prepare snap points (optimizes snapping for later) switch (mode) { @@ -497,11 +631,145 @@ bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, return true; } -rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, - const QVector ghosts, - bool prevent_overwriting) +struct TrackBlockListPair { + TrackReference track; + QList blocks; +}; + +void TimelineWidget::PointerTool::ProcessGhostsForSliding() { - foreach (TimelineViewGhostItem* ghost, ghosts) { + // Sort blocks into tracks + QList blocks_per_track; + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); + bool found = false; + + for (int i=0;iTrack()) { + blocks_per_track[i].blocks.append(b); + found = true; + break; + } + } + + if (!found) { + blocks_per_track.append({ghost->Track(), {b}}); + } + } + + // Make contiguous runs of blocks per each track + foreach (const TrackBlockListPair& p, blocks_per_track) { + // Blocks must be merged if any are non-adjacent + const TrackReference& track = p.track; + const QList& blocks = p.blocks; + + Block* earliest_block = blocks.first(); + Block* latest_block = blocks.first(); + + // Find the earliest and latest selected blocks + for (int j=1;jin() < earliest_block->in()) { + earliest_block = compare; + } + + if (compare->in() > latest_block->in()) { + latest_block = compare; + } + } + + // Add any blocks between these blocks that aren't already in the list + if (earliest_block != latest_block) { + Block* b = earliest_block; + while ((b = b->next()) != latest_block) { + if (!blocks.contains(b)) { + TimelineViewGhostItem* g = AddGhostFromBlock(b, track, Timeline::kMove, true); + g->setData(TimelineViewGhostItem::kPointerToolMode, kSlide); + } + } + } + + // Add surrounding blocks that will be trimming instead of moving + if (earliest_block->previous()) { + TimelineViewGhostItem* g = AddGhostFromBlock(earliest_block->previous(), track, Timeline::kTrimOut, true); + g->setData(TimelineViewGhostItem::kPointerToolMode, kSlide); + } + + if (latest_block->next()) { + TimelineViewGhostItem* g = AddGhostFromBlock(latest_block->next(), track, Timeline::kTrimIn, true); + g->setData(TimelineViewGhostItem::kPointerToolMode, kSlide); + } + } +} + +void TimelineWidget::PointerTool::ProcessGhostsForRolling() +{ + // For each ghost, we make an equivalent Ghost on the next/previous block + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + Block* ghost_block = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); + + if (ghost->mode() == Timeline::kTrimIn && ghost_block->previous()) { + // Add an extra Ghost for the previous block + AddGhostFromBlock(ghost_block->previous(), ghost->Track(), Timeline::kTrimOut, true); + } else if (ghost->mode() == Timeline::kTrimOut && ghost_block->next()) { + AddGhostFromBlock(ghost_block->next(), ghost->Track(), Timeline::kTrimIn, true); + } + } +} + +bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block, + const TrackReference& track, + Timeline::MovementMode movement, + const QList& selected_items) +{ + // Assume block is a clip and see if it has any transitions + TransitionBlock* transitions[2]; + + if (movement == Timeline::kMove || movement == Timeline::kTrimOut) { + transitions[0] = TransitionBlock::GetBlockOutTransition(block); + } else { + transitions[0] = nullptr; + } + + if (movement == Timeline::kMove || movement == Timeline::kTrimIn) { + transitions[1] = TransitionBlock::GetBlockInTransition(block); + } else { + transitions[1] = nullptr; + } + + bool ret = false; + + for (int i=0;i<2;i++) { + if (!transitions[i]) { + continue; + } + + bool found = false; + + foreach (TimelineViewBlockItem* item, selected_items) { + if (item->block() == transitions[i]) { + // Do nothing + found = true; + break; + } + } + + if (!found) { + TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(transitions[i], track, + Timeline::kMove, false); + transition_ghost->setData(TimelineViewGhostItem::kPointerToolMode, kPointer); + + ret = true; + } + } + + return ret; +} + +rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement) +{ + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { if (ghost->mode() != Timeline::kTrimIn) { continue; } @@ -516,29 +784,7 @@ rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, } if (block) { - /* FIXME: Rewrite transition logic - if (block->type() == Block::kTransition) { - // For transitions, validate with the attached block - TransitionBlock* transition = static_cast(block); - - if (transition->connected_in_block() && transition->connected_out_block()) { - // Here, we try to get the latest earliest point for both the in and out blocks, we do in here and out will - // be calculated later - earliest_in = GetEarliestPointForClip(transition->connected_in_block()); - - // We set the block to the out block since that will be before the in block and will be the one we use to - // prevent overwriting since we're trimming the in side of this transition - block = transition->connected_out_block(); - - latest_in = transition->in() + transition->out_offset(); - } else { - // Use whatever block is attached - block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block(); - } - } - */ - - if (prevent_overwriting) { + if (!ghost->data(TimelineViewGhostItem::kTrimOverwriteAllowed).toBool()) { // Look for a Block in the way Block* prev = block->previous(); while (prev != nullptr) { @@ -566,11 +812,9 @@ rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, return movement; } -rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement, - const QVector ghosts, - bool prevent_overwriting) +rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement) { - foreach (TimelineViewGhostItem* ghost, ghosts) { + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { if (ghost->mode() != Timeline::kTrimOut) { continue; } @@ -588,27 +832,7 @@ rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement, // Ripple tool creates block-less ghosts and creates gaps with them later if (block) { - /* FIXME: Rewrite transition logic - if (block->type() == Block::kTransition) { - // For transitions, validate with the attached block - TransitionBlock* transition = static_cast(block); - - if (transition->connected_in_block() && transition->connected_out_block()) { - // We set the block to the out block since that will be before the in block and will be the one we use to - // prevent overwriting since we're trimming the in side of this transition - - // FIXME: At some point we may add some better logic to `latest_out` akin to the logic in ValidateInTrimming - // which is why this hasn't yet been collapsed into the ternary below. - block = transition->connected_in_block(); - - earliest_out = transition->out() - transition->in_offset(); - } else { - block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block(); - } - } - */ - - if (prevent_overwriting) { + if (!ghost->data(TimelineViewGhostItem::kTrimOverwriteAllowed).toBool()) { // Determine if there's a block in the way Block* next = block->next(); while (next != nullptr) { diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index 9fb88ff40..a03303b1a 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -29,14 +29,13 @@ TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) : PointerTool(parent) { SetMovementAllowed(false); - SetTrimOverwriteAllowed(true); SetGapTrimmingAllowed(true); } void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { - PointerTool::InitiateDrag(clicked_item, trim_mode); + InitiateDragInternal(clicked_item, trim_mode, kPointer, true); if (parent()->ghost_items_.isEmpty()) { return; @@ -86,16 +85,16 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite 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); + ghost = AddGhostFromBlock(block_before_ripple, track_ref, trim_mode, true); } else if (block_before_ripple->next()) { // Assuming this block is NOT at the end of the track (i.e. next != null) // We're going to create a gap after it. If next is a gap, we can just use that if (block_before_ripple->next()->type() == Block::kGap) { - ghost = AddGhostFromBlock(block_before_ripple->next(), track_ref, trim_mode); + ghost = AddGhostFromBlock(block_before_ripple->next(), track_ref, trim_mode, true); } else { // If next is NOT a gap, we'll need to create one, for which we'll use a null ghost - ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode); + ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode, true); ghost->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple)); } } diff --git a/app/widget/timelinewidget/tool/rolling.cpp b/app/widget/timelinewidget/tool/rolling.cpp index 6baf25316..8f9f132c9 100644 --- a/app/widget/timelinewidget/tool/rolling.cpp +++ b/app/widget/timelinewidget/tool/rolling.cpp @@ -29,47 +29,13 @@ TimelineWidget::RollingTool::RollingTool(TimelineWidget* parent) : PointerTool(parent) { SetMovementAllowed(false); - SetTrimOverwriteAllowed(true); SetGapTrimmingAllowed(true); } void TimelineWidget::RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { - PointerTool::InitiateDrag(clicked_item, trim_mode); - - // For each ghost, we make an equivalent Ghost on the next/previous block - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - Block* ghost_block = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - - if (ghost->mode() == Timeline::kTrimIn && ghost_block->previous()) { - // Add an extra Ghost for the previous block - AddGhostFromBlock(ghost_block->previous(), ghost->Track(), Timeline::kTrimOut); - } else if (ghost->mode() == Timeline::kTrimOut && ghost_block->next()) { - AddGhostFromBlock(ghost_block->next(), ghost->Track(), Timeline::kTrimIn); - } - } -} - -void TimelineWidget::RollingTool::FinishDrag(TimelineViewMouseEvent *event) -{ - QUndoCommand* command = new QUndoCommand(); - - // Find earliest point to ripple around - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (ghost->mode() == drag_movement_mode()) { - Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - - BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->Track()), - b, - ghost->AdjustedLength(), - drag_movement_mode(), - command); - c->SetAllowNonGapTrimming(true); - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); + InitiateDragInternal(clicked_item, trim_mode, kRolling, true); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/tool/slide.cpp b/app/widget/timelinewidget/tool/slide.cpp index 26423f335..f9bd8f357 100644 --- a/app/widget/timelinewidget/tool/slide.cpp +++ b/app/widget/timelinewidget/tool/slide.cpp @@ -30,105 +30,13 @@ TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) : { SetTrimmingAllowed(false); SetTrackMovementAllowed(false); - SetTrimOverwriteAllowed(true); SetGapTrimmingAllowed(true); } -struct TrackBlockListPair { - TrackReference track; - QList blocks; -}; - void TimelineWidget::SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { - PointerTool::InitiateDrag(clicked_item, trim_mode); - - // Sort blocks into tracks - QList blocks_per_track; - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - bool found = false; - - for (int i=0;iTrack()) { - blocks_per_track[i].blocks.append(b); - found = true; - break; - } - } - - if (!found) { - blocks_per_track.append({ghost->Track(), {b}}); - } - } - - // Make contiguous runs of blocks per each track - foreach (const TrackBlockListPair& p, blocks_per_track) { - // Blocks must be merged if any are non-adjacent - const TrackReference& track = p.track; - const QList& blocks = p.blocks; - - Block* earliest_block = blocks.first(); - Block* latest_block = blocks.first(); - - // Find the earliest and latest selected blocks - for (int j=1;jin() < earliest_block->in()) { - earliest_block = compare; - } - - if (compare->in() > latest_block->in()) { - latest_block = compare; - } - } - - // Add any blocks between these blocks that aren't already in the list - if (earliest_block != latest_block) { - Block* b = earliest_block; - while ((b = b->next()) != latest_block) { - if (!blocks.contains(b)) { - AddGhostFromBlock(b, track, Timeline::kMove); - } - } - } - - // Add surrounding blocks that will be trimming instead of moving - if (earliest_block->previous()) { - AddGhostFromBlock(earliest_block->previous(), track, Timeline::kTrimOut); - } - - if (latest_block->next()) { - AddGhostFromBlock(latest_block->next(), track, Timeline::kTrimIn); - } - } -} - -void TimelineWidget::SlideTool::FinishDrag(TimelineViewMouseEvent *event) -{ - Q_UNUSED(event) - - QVector info; - - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (!ghost->HasBeenAdjusted()) { - continue; - } - - Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - - info.append({parent()->GetTrackFromReference(ghost->Track()), - b, - ghost->mode(), - ghost->mode() == Timeline::kMove ? ghost->GetAdjustedIn() : ghost->AdjustedLength(), - ghost->mode() == Timeline::kMove ? ghost->In() : ghost->Length()}); - } - - if (!info.isEmpty()) { - Core::instance()->undo_stack()->push(new TrackSlideCommand(info)); - } + InitiateDragInternal(clicked_item, trim_mode, kSlide, true); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 51f9425ce..502755acb 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -73,9 +73,9 @@ TimelineViewBlockItem *TimelineWidget::Tool::GetItemAtScenePos(const TimelineCoo return nullptr; } -rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVector ghosts) +rational TimelineWidget::Tool::ValidateTimeMovement(rational movement) { - foreach (TimelineViewGhostItem* ghost, ghosts) { + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { if (ghost->mode() != Timeline::kMove) { continue; } @@ -106,7 +106,7 @@ rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVe return movement; } -int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector ghosts) +int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector& ghosts) { foreach (TimelineViewGhostItem* ghost, ghosts) { if (ghost->mode() != Timeline::kMove) { @@ -115,7 +115,7 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector