From ac207e8fe793f1102039626460f0e9a0588ef05a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 12:09:10 -0700 Subject: [PATCH] timeline: implement add default transition Fixes #1939 --- app/config/config.cpp | 4 + app/panel/timeline/timeline.h | 5 + app/widget/menu/menushared.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 16 +++ app/widget/timelinewidget/timelinewidget.h | 2 + .../undo/timelineundogeneral.cpp | 132 +++++++++++++++++- .../timelinewidget/undo/timelineundogeneral.h | 55 ++++++++ 7 files changed, 213 insertions(+), 3 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 43668c467..4b62bbacc 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -104,6 +104,10 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); + SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); + SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); + SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed); diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index cf596a724..da0418e46 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -86,6 +86,11 @@ public: virtual void MoveOutToPlayhead() override; + void AddDefaultTransitionsToSelected() + { + timeline_widget()->AddDefaultTransitionsToSelected(); + } + void ShowSpeedDurationDialogForSelectedClips() { timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 6dc566e31..431e72f72 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -291,7 +291,7 @@ void MenuShared::NestTriggered() void MenuShared::DefaultTransitionTriggered() { - qDebug() << "FIXME: Stub"; + PanelManager::instance()->MostRecentlyFocused()->AddDefaultTransitionsToSelected(); } void MenuShared::TimecodeDisplayTriggered() diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index c729a6266..3dc750a86 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -568,6 +568,22 @@ void TimelineWidget::ToggleLinksOnSelected() Core::instance()->undo_stack()->push(new NodeLinkManyCommand(blocks, link)); } +void TimelineWidget::AddDefaultTransitionsToSelected() +{ + QVector blocks; + + foreach (Block* item, GetSelectedBlocks()) { + // Only clips can be linked + if (ClipBlock *clip = dynamic_cast(item)) { + blocks.append(clip); + } + } + + if (!blocks.isEmpty()) { + Core::instance()->undo_stack()->push(new TimelineAddDefaultTransitionCommand(blocks, timebase())); + } +} + bool TimelineWidget::CopySelected(bool cut) { if (super::CopySelected(cut)) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 532b2b600..58846f53a 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -79,6 +79,8 @@ public: void ToggleLinksOnSelected(); + void AddDefaultTransitionsToSelected(); + virtual bool CopySelected(bool cut) override; virtual bool Paste() override; diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index c90d8b078..d774d47e6 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -22,9 +22,11 @@ #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" +#include "node/factory.h" #include "node/math/math/math.h" #include "node/math/merge/merge.h" #include "timelineundocommon.h" +#include "widget/timelinewidget/undo/timelineundotrack.h" namespace olive { @@ -287,8 +289,8 @@ void TrackListInsertGaps::prepare() QVector blocks_to_append_gap_to; QVector tracks_to_append_gap_to; - foreach (Track* track, working_tracks_) { - foreach (Block* b, track->Blocks()) { + for (Track* track : qAsConst(working_tracks_)) { + for (Block* b : track->Blocks()) { if (dynamic_cast(b) && b->in() <= point_ && b->out() >= point_) { // Found a gap at the location gaps_to_extend_.append(b); @@ -542,4 +544,130 @@ void TimelineRemoveTrackCommand::undo() remove_command_->undo_now(); } +void TimelineAddDefaultTransitionCommand::prepare() +{ + for (auto it=clips_.cbegin(); it!=clips_.cend(); it++) { + ClipBlock *c = *it; + + // Handle in transition + if (clips_.contains(static_cast(c->previous()))) { + // Do nothing, assume this will be handled by a dual transition from that clip + } else if (dynamic_cast(c->previous()) || !c->previous()) { + // Create in transition + AddTransition(c, kIn); + } + + // Handle out transition + if (clips_.contains(static_cast(c->next()))) { + AddTransition(c, kOutDual); + } else if (dynamic_cast(c->next()) || !c->next()) { + // Create out transition + AddTransition(c, kOut); + } + } +} + +void TimelineAddDefaultTransitionCommand::AddTransition(ClipBlock *c, CreateTransitionMode mode) +{ + if (Track *t = c->track()) { + Node *p = nullptr; + if (t->type() == Track::kVideo) { + p = NodeFactory::CreateFromID(OLIVE_CONFIG("DefaultVideoTransition").toString()); + } else if (t->type() == Track::kAudio) { + p = NodeFactory::CreateFromID(OLIVE_CONFIG("DefaultAudioTransition").toString()); + } + + rational transition_length = OLIVE_CONFIG("DefaultTransitionLength").value(); + + // Resize original clip + switch (mode) { + case kIn: + ValidateTransitionLength(c, transition_length); + + if (transition_length > 0) { + AdjustClipLength(c, transition_length, false); + } + break; + case kOut: + ValidateTransitionLength(c, transition_length); + + if (transition_length > 0) { + AdjustClipLength(c, transition_length, true); + } + break; + case kOutDual: + { + rational half_length = transition_length / 2; + + ValidateTransitionLength(static_cast(c->next()), half_length); + ValidateTransitionLength(c, half_length); + + transition_length = half_length * 2; + + if (transition_length > 0) { + AdjustClipLength(static_cast(c->next()), half_length, false); + AdjustClipLength(c, half_length, true); + } + break; + } + } + + if (transition_length > 0) { + if (TransitionBlock *transition = dynamic_cast(p)) { + transition->set_length_and_media_out(transition_length); + + // Add transition + commands_.append(new NodeAddCommand(c->parent(), transition)); + + // Insert block + Block *insert_after; + switch (mode) { + case kIn: + insert_after = c->previous(); + break; + case kOut: + case kOutDual: + insert_after = c; + break; + } + commands_.append(new TrackInsertBlockAfterCommand(c->track(), transition, insert_after)); + + // Connect + switch (mode) { + case kIn: + commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kInBlockInput))); + break; + case kOutDual: + commands_.append(new NodeEdgeAddCommand(c->next(), NodeInput(transition, TransitionBlock::kInBlockInput))); + /* fall through */ + case kOut: + commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kOutBlockInput))); + break; + } + } + } + } +} + +void TimelineAddDefaultTransitionCommand::AdjustClipLength(ClipBlock *c, const rational &transition_length, bool out) +{ + rational cur_len = lengths_.value(c, c->length()); + rational new_len = cur_len - transition_length; + if (out) { + commands_.append(new BlockResizeCommand(c, new_len)); + } else { + commands_.append(new BlockResizeWithMediaInCommand(c, new_len)); + } + lengths_.insert(c, new_len); +} + +void TimelineAddDefaultTransitionCommand::ValidateTransitionLength(ClipBlock *c, rational &transition_length) +{ + rational cur_len = lengths_.value(c, c->length()); + rational half_cur_len = cur_len/2; + if (transition_length >= half_cur_len) { + transition_length = half_cur_len - timebase_; + } +} + } diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.h b/app/widget/timelinewidget/undo/timelineundogeneral.h index 55f36b7b9..50d04fe09 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.h +++ b/app/widget/timelinewidget/undo/timelineundogeneral.h @@ -358,6 +358,61 @@ private: }; +class TimelineAddDefaultTransitionCommand : public UndoCommand +{ +public: + TimelineAddDefaultTransitionCommand(const QVector &clips, const rational &timebase) : + clips_(clips), + timebase_(timebase) + {} + + virtual ~TimelineAddDefaultTransitionCommand() override + { + qDeleteAll(commands_); + } + + virtual Project* GetRelevantProject() const override + { + return clips_.empty() ? nullptr : clips_.first()->project(); + } + +protected: + virtual void prepare() override; + + virtual void redo() override + { + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { + (*it)->redo_now(); + } + } + + virtual void undo() override + { + for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) { + (*it)->undo_now(); + } + } + +private: + enum CreateTransitionMode { + kIn, + kOut, + kOutDual + }; + + void AddTransition(ClipBlock *c, CreateTransitionMode mode); + void AdjustClipLength(ClipBlock *c, const rational &transition_length, bool out); + void ValidateTransitionLength(ClipBlock *c, rational &transition_length); + + + QVector clips_; + rational timebase_; + QVector commands_; + + QHash lengths_; + +}; + } #endif // TIMELINEUNDOGENERAL_H