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_;