From b5cc13c10e7459c87258251229f886dace9b3482 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 25 Nov 2020 11:46:41 +1100 Subject: [PATCH] timeline: snap pointer and import operations to timebase If frame rates are mixed, ensures that video doesn't end up falling or ending on half frames which potentially make the entire sequence awkward --- app/widget/timelinewidget/timelinewidget.cpp | 13 ++++---- app/widget/timelinewidget/timelinewidget.h | 4 +-- app/widget/timelinewidget/tool/import.cpp | 11 +++++-- app/widget/timelinewidget/tool/pointer.cpp | 30 +++++++++++++++---- app/widget/timelinewidget/tool/slip.cpp | 5 ++-- app/widget/timelinewidget/tool/tool.cpp | 19 ++++++++++++ app/widget/timelinewidget/tool/tool.h | 2 ++ app/widget/timelinewidget/undo/undo.cpp | 7 ++++- .../timelinewidget/view/timelineview.cpp | 7 +++-- .../view/timelineviewghostitem.cpp | 11 ------- 10 files changed, 75 insertions(+), 34 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 86d5856d4..449f876de 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -353,14 +353,6 @@ void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, X } } -rational TimelineWidget::GetToolTipTimebase() const -{ - if (GetConnectedNode() && use_audio_time_units_) { - return GetConnectedNode()->audio_params().time_base(); - } - return timebase(); -} - void TimelineWidget::SelectAll() { QVector newly_selected_blocks; @@ -1211,6 +1203,11 @@ TimelineView *TimelineWidget::GetFirstTimelineView() return views_.first()->view(); } +rational TimelineWidget::GetTimebaseForTrackType(Timeline::TrackType type) +{ + return views_.at(type)->view()->timebase(); +} + const QRect& TimelineWidget::GetRubberBandGeometry() const { return rubberband_.geometry(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 3eb0d94d4..7c0ec3a2b 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -154,8 +154,6 @@ public: return !ghost_items_.isEmpty(); } - rational GetToolTipTimebase() const; - bool IsBlockSelected(Block* b) const { return selected_blocks_.contains(b); @@ -167,6 +165,8 @@ public: TimelineView* GetFirstTimelineView(); + rational GetTimebaseForTrackType(Timeline::TrackType type); + const QRect &GetRubberBandGeometry() const; /** diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 930d7f71d..da9aa2bb1 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -149,9 +149,10 @@ void ImportTool::DragMove(TimelineViewMouseEvent *event) } // Generate tooltip (showing earliest in point of imported clip) - int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, parent()->GetToolTipTimebase()); + rational tooltip_timebase = parent()->GetTimebaseForTrackType(event->GetTrack().type()); + int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, tooltip_timebase); QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp, - parent()->GetToolTipTimebase(), + tooltip_timebase, Core::instance()->GetTimecodeDisplay()); // Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way @@ -271,6 +272,12 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QList(); } + // Snap footage duration to timebase + rational snap_mvmt = SnapMovementToTimebase(footage_duration, 0, dest_tb); + if (!snap_mvmt.isNull()) { + footage_duration += snap_mvmt; + } + foreach (TimelineViewGhostItem* ghost, footage_ghosts) { ghost->SetIn(ghost_start); ghost->SetOut(ghost_start + footage_duration); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 4edf6eed9..768483bcd 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -469,7 +469,7 @@ void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) } // Validate ghosts that are being moved (clips from other track types do NOT get moved) - { + if (track_movement != 0) { QVector validate_track_ghosts = parent()->GetGhostItems(); for (int i=0;iGetTrack().type() != drag_track_type_) { @@ -508,10 +508,11 @@ void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) // Regenerate tooltip and force it to update (otherwise the tooltip won't move as written in the // documentation, and could get in the way of the cursor) + rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase()), - parent()->GetToolTipTimebase(), + Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, tooltip_timebase), + tooltip_timebase, Core::instance()->GetTimecodeDisplay(), true), parent()); @@ -872,6 +873,8 @@ bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, rational PointerTool::ValidateInTrimming(rational movement) { + bool first_ghost = true; + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (ghost->GetMode() != Timeline::kTrimIn) { continue; @@ -880,8 +883,11 @@ rational PointerTool::ValidateInTrimming(rational movement) rational earliest_in = RATIONAL_MIN; rational latest_in = ghost->GetOut(); + rational ghost_timebase = parent()->GetTimebaseForTrackType(ghost->GetTrack().type()); + + // If the ghost must be at least one frame in size, limit the latest allowed in point if (!ghost->CanHaveZeroLength()) { - latest_in -= parent()->timebase(); + latest_in -= ghost_timebase; } // Clamp adjusted value between the earliest and latest values @@ -891,6 +897,11 @@ rational PointerTool::ValidateInTrimming(rational movement) if (clamped != adjusted) { movement = clamped - ghost->GetIn(); } + + if (first_ghost) { + movement = SnapMovementToTimebase(ghost->GetIn(), movement, ghost_timebase); + first_ghost = false; + } } return movement; @@ -898,6 +909,8 @@ rational PointerTool::ValidateInTrimming(rational movement) rational PointerTool::ValidateOutTrimming(rational movement) { + bool first_ghost = true; + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (ghost->GetMode() != Timeline::kTrimOut) { continue; @@ -906,8 +919,10 @@ rational PointerTool::ValidateOutTrimming(rational movement) // Determine earliest and latest out points rational earliest_out = ghost->GetIn(); + rational ghost_timebase = parent()->GetTimebaseForTrackType(ghost->GetTrack().type()); + if (!ghost->CanHaveZeroLength()) { - earliest_out += parent()->timebase(); + earliest_out += ghost_timebase; } rational latest_out = RATIONAL_MAX; @@ -919,6 +934,11 @@ rational PointerTool::ValidateOutTrimming(rational movement) if (clamped != adjusted) { movement = clamped - ghost->GetOut(); } + + if (first_ghost) { + movement = SnapMovementToTimebase(ghost->GetOut(), movement, ghost_timebase); + first_ghost = false; + } } return movement; diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp index edf788138..1570791df 100644 --- a/app/widget/timelinewidget/tool/slip.cpp +++ b/app/widget/timelinewidget/tool/slip.cpp @@ -54,10 +54,11 @@ void SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) // Generate tooltip and force it to to update (otherwise the tooltip won't move as written in the // documentation, and could get in the way of the cursor) + rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase()), - parent()->GetToolTipTimebase(), + Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, tooltip_timebase), + tooltip_timebase, Core::instance()->GetTimecodeDisplay(), true), parent()); diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 633bd5c5b..082054443 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -53,8 +53,22 @@ Timeline::MovementMode TimelineTool::FlipTrimMode(const Timeline::MovementMode & return trim_mode; } +rational TimelineTool::SnapMovementToTimebase(const rational &start, rational movement, const rational &timebase) +{ + rational proposed_position = start + movement; + rational snapped = Timecode::snap_time_to_timebase(proposed_position, timebase); + + if (proposed_position != snapped) { + movement += snapped - proposed_position; + } + + return movement; +} + rational TimelineTool::ValidateTimeMovement(rational movement) { + bool first_ghost = true; + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (ghost->GetMode() != Timeline::kMove) { continue; @@ -63,6 +77,11 @@ rational TimelineTool::ValidateTimeMovement(rational movement) // Prevents any ghosts from going below 0:00:00 time if (ghost->GetIn() + movement < 0) { movement = -ghost->GetIn(); + } else if (first_ghost) { + // Ensure ghost is snapped to a grid + movement = SnapMovementToTimebase(ghost->GetIn(), movement, parent()->GetTimebaseForTrackType(ghost->GetTrack().type())); + + first_ghost = false; } } diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index 544fe7be9..ffb5f185b 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -53,6 +53,8 @@ public: static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode); + static rational SnapMovementToTimebase(const rational& start, rational movement, const rational& timebase); + protected: /** * @brief Validates Ghosts that are moving horizontally (time-based) diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 5f987d234..32edcefd1 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -165,6 +165,7 @@ TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, r in_(in), out_(out), splice_(false), + splice_split_command_(nullptr), trim_out_(nullptr), trim_in_(nullptr), insert_(nullptr) @@ -305,7 +306,6 @@ void TrackRippleRemoveAreaCommand::undo_internal() trim_out_->set_length_and_media_out(trim_out_old_length_); splice_split_command_->undo(); - delete splice_split_command_; } else { @@ -344,6 +344,11 @@ void TrackRippleRemoveAreaCommand::undo_internal() track_->Node::InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX), track_->block_input(), track_->block_input()); + + if (splice_split_command_) { + delete splice_split_command_; + splice_split_command_ = nullptr; + } } TrackPlaceBlockCommand::TrackPlaceBlockCommand(TrackList *timeline, int track, Block *block, rational in, QUndoCommand *parent) : diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index d9cd2ced8..841a02b0b 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -115,13 +115,13 @@ void TimelineView::wheelEvent(QWheelEvent *event) return; } else { #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) - + QPoint angle_delta = event->angleDelta(); if (Config::Current()["InvertTimelineScrollAxes"].toBool()) { angle_delta = QPoint(angle_delta.y(), angle_delta.x()); } - + QWheelEvent e( #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) event->position(), @@ -255,7 +255,8 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) painter->setBrush(Qt::NoBrush); foreach (TimelineViewGhostItem* ghost, (*ghosts_)) { - if (ghost->GetTrack().type() == connected_track_list_->type()) { + if (ghost->GetTrack().type() == connected_track_list_->type() + && !ghost->IsInvisible()) { int track_index = ghost->GetAdjustedTrack().index(); painter->drawRect(TimeToScene(ghost->GetAdjustedIn()), diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.cpp b/app/widget/timelinewidget/view/timelineviewghostitem.cpp index e816094d8..7762fbafb 100644 --- a/app/widget/timelinewidget/view/timelineviewghostitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewghostitem.cpp @@ -74,17 +74,6 @@ void TimelineViewGhostItem::SetCanMoveTracks(bool e) can_move_tracks_ = e; } -/*void TimelineViewGhostItem::SetInvisible(bool invisible) -{ - setBrush(Qt::NoBrush); - - if (invisible) { - setPen(Qt::NoPen); - } else { - setPen(QPen(Qt::yellow, 2)); // FIXME: Make customizable via CSS - } -}*/ - const rational &TimelineViewGhostItem::GetIn() const { return in_;