diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index d208f80da..0593da154 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -109,7 +109,7 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return RetrieveVideoInternal(renderer, timecode, divider, cancelled); } -Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode) +Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); @@ -280,7 +280,7 @@ bool Decoder::ConformAudioInternal(const QVector &filenames, const Audi return false; } -bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { @@ -290,7 +290,7 @@ bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVecto const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); while (write_index < buffer_length_in_bytes) { - if (loop_mode == Footage::kLoopModeLoop) { + if (loop_mode == kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index dff4cdb40..7fa55c5b0 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -35,7 +35,6 @@ extern "C" { #include "codec/samplebuffer.h" #include "common/rational.h" #include "node/block/block.h" -#include "node/project/footage/footage.h" #include "node/project/footage/footagedescription.h" #include "task/task.h" @@ -72,6 +71,12 @@ public: kIndexUnavailable }; + enum LoopMode { + kLoopModeOff, + kLoopModeLoop, + kLoopModeClamp + }; + Decoder(); /** @@ -209,7 +214,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode); + RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode); /** * @brief Determine the last time this decoder instance was used in any way @@ -316,7 +321,7 @@ signals: private: void UpdateLastAccessed(); - bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp index 580da35d8..7214ca27c 100644 --- a/app/dialog/speedduration/speeddurationdialog.cpp +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -38,12 +38,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons clips_(clips), timebase_(timebase) { - setWindowTitle(tr("Speed/Duration")); + setWindowTitle(tr("Clip Properties")); QVBoxLayout *layout = new QVBoxLayout(this); { - QGroupBox *speed_group = new QGroupBox(); + QGroupBox *speed_group = new QGroupBox(tr("Speed/Duration")); layout->addWidget(speed_group); QGridLayout *speed_layout = new QGridLayout(speed_group); @@ -72,16 +72,39 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons link_box_ = new QCheckBox(tr("Link Speed and Duration")); link_box_->setChecked(true); speed_layout->addWidget(link_box_, row, 0, 1, 2); + + row++; + + reverse_box_ = new QCheckBox(tr("Reverse")); + speed_layout->addWidget(reverse_box_, row, 0, 1, 2); + + row++; + + maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch")); + speed_layout->addWidget(maintain_audio_pitch_box_, row, 0, 1, 2); + + row++; + + ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); + speed_layout->addWidget(ripple_box_, row, 0, 1, 2); } - reverse_box_ = new QCheckBox(tr("Reverse")); - layout->addWidget(reverse_box_); + { + auto loop_box = new QGroupBox(tr("Loop")); + layout->addWidget(loop_box); - maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch")); - layout->addWidget(maintain_audio_pitch_box_); + auto loop_layout = new QGridLayout(loop_box); - ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); - layout->addWidget(ripple_box_); + int row = 0; + + loop_layout->addWidget(new QLabel(tr("Loop:")), row, 0); + + loop_combo_ = new QComboBox(); + loop_combo_->addItem(tr("None"), Decoder::kLoopModeOff); + loop_combo_->addItem(tr("Loop"), Decoder::kLoopModeLoop); + loop_combo_->addItem(tr("Clamp"), Decoder::kLoopModeClamp); + loop_layout->addWidget(loop_combo_, row, 1); + } QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); btns->setCenterButtons(true); @@ -94,6 +117,7 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons start_duration_ = clips.first()->length(); start_reverse_ = clips.first()->reverse(); start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch(); + start_loop_ = clips.first()->loop_mode(); for (int i=1; i &clips, cons if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) { start_maintain_audio_pitch_ = -1; } + + if (start_loop_ != -1 && c->loop_mode() != start_loop_) { + start_loop_ = -1; + } } if (qIsNaN(start_speed_)) { @@ -141,6 +169,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons } else { maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_); } + + if (start_loop_ == -1) { + loop_combo_->setCurrentIndex(-1); + } else { + loop_combo_->setCurrentIndex(start_loop_); + } } void SpeedDurationDialog::accept() @@ -211,6 +245,12 @@ void SpeedDurationDialog::accept() } } + if (loop_combo_->currentIndex() != -1) { + foreach (ClipBlock *c, clips_) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kLoopModeInput)), loop_combo_->currentData())); + } + } + Core::instance()->undo_stack()->push(command); super::accept(); diff --git a/app/dialog/speedduration/speeddurationdialog.h b/app/dialog/speedduration/speeddurationdialog.h index 82d95d12e..3e97d718d 100644 --- a/app/dialog/speedduration/speeddurationdialog.h +++ b/app/dialog/speedduration/speeddurationdialog.h @@ -22,6 +22,7 @@ #define SPEEDDURATIONDIALOG_H #include +#include #include #include "node/block/clip/clip.h" @@ -62,6 +63,8 @@ private: QCheckBox *ripple_box_; + QComboBox *loop_combo_; + int start_reverse_; int start_maintain_audio_pitch_; @@ -70,6 +73,8 @@ private: rational start_duration_; + int start_loop_; + rational timebase_; private slots: diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 9d5e90b0c..1b8fed631 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -34,6 +34,7 @@ const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in"); const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in"); const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in"); const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in"); +const QString ClipBlock::kLoopModeInput = QStringLiteral("loop_in"); ClipBlock::ClipBlock() : in_transition_(nullptr), @@ -56,6 +57,8 @@ ClipBlock::ClipBlock() : //SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); SetEffectInput(kBufferIn); + + AddInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); } QString ClipBlock::Name() const @@ -89,7 +92,8 @@ void ClipBlock::set_length_and_media_out(const rational &length) if (reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, true); + + rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreReverse | kSTMIgnoreLoop); set_media_in(proposed_media_in); } @@ -104,7 +108,7 @@ void ClipBlock::set_length_and_media_in(const rational &length) if (!reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true); + rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreSpeed | kSTMIgnoreLoop); waveform_.TrimIn(proposed_media_in - media_in()); @@ -127,7 +131,7 @@ void ClipBlock::set_media_in(const rational &media_in) SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); } -rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const +rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, uint64_t flags) const { // These constants are not considered "values" per se, so we don't modify them if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) { @@ -136,11 +140,11 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno rational media_time = sequence_time; - if (reverse() && !ignore_reverse) { + if (reverse() && !(flags & kSTMIgnoreReverse)) { media_time = length() - media_time; } - if (!ignore_speed) { + if (!(flags & kSTMIgnoreSpeed)) { double speed_value = speed(); if (qIsNull(speed_value)) { // Effectively holds the frame at the in point @@ -153,6 +157,23 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno media_time += media_in(); + /*if (!(flags & kSTMIgnoreLoop) + && this->loop_mode() != kLoopModeOff + && connected_viewer_ + && !connected_viewer_->GetLength().isNull() + && (media_time < 0 || media_time >= connected_viewer_->GetLength())) { + if (loop_mode() == kLoopModeLoop) { + while (media_time < 0) { + media_time += connected_viewer_->GetLength(); + } + while (media_time >= connected_viewer_->GetLength()) { + media_time -= connected_viewer_->GetLength(); + } + } else if (loop_mode() == kLoopModeClamp) { + media_time = std::clamp(media_time, rational(0), connected_viewer_->GetLength()-connected_viewer_->GetVideoParams().frame_rate_as_time_base()); + } + }*/ + return media_time; } @@ -282,6 +303,8 @@ void ClipBlock::Retranslate() SetInputName(kSpeedInput, tr("Speed")); SetInputName(kReverseInput, tr("Reverse")); SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); + SetInputName(kLoopModeInput, tr("Loop")); + SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")}); } TimeRange ClipBlock::media_range() const diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 6541a8710..6b625eb53 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -22,6 +22,7 @@ #define CLIPBLOCK_H #include "audio/audiovisualwaveform.h" +#include "codec/decoder.h" #include "node/block/block.h" namespace olive { @@ -121,17 +122,39 @@ public: TimeRange media_range() const; + /** + * @brief Get currently set loop mode + */ + Decoder::LoopMode loop_mode() const + { + return static_cast(GetStandardValue(kLoopModeInput).toInt()); + } + + void set_loop_mode(Decoder::LoopMode l) + { + SetStandardValue(kLoopModeInput, int(l)); + } + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; static const QString kReverseInput; static const QString kMaintainAudioPitchInput; + static const QString kLoopModeInput; protected: virtual void LinkChangeEvent() override; private: - rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const; + enum SequenceToMediaTimeFlag + { + kSTMNone, + kSTMIgnoreReverse, + kSTMIgnoreSpeed, + kSTMIgnoreLoop + }; + + rational SequenceToMediaTime(const rational& sequence_time, uint64_t flags = kSTMNone) const; rational MediaToSequenceTime(const rational& media_time) const; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 0832b2f0a..8c4ca6505 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -37,7 +37,6 @@ namespace olive { const QString Footage::kFilenameInput = QStringLiteral("file_in"); -const QString Footage::kLoopModeInput = QStringLiteral("loop_in"); #define super ViewerOutput @@ -49,8 +48,6 @@ Footage::Footage(const QString &filename) : { SetCacheTextures(true); - PrependInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); - PrependInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); Clear(); @@ -70,8 +67,6 @@ void Footage::Retranslate() super::Retranslate(); SetInputName(kFilenameInput, tr("Filename")); - SetInputName(kLoopModeInput, tr("Loop Mode")); - SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")}); } void Footage::InputValueChangedEvent(const QString &input, int element) @@ -139,11 +134,6 @@ void Footage::SetValid() valid_ = true; } -Footage::LoopMode Footage::loop_mode() const -{ - return static_cast(GetStandardValue(kLoopModeInput).toInt()); -} - QString Footage::filename() const { return GetStandardValue(kFilenameInput).toString(); @@ -263,17 +253,15 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Pop filename from table QString file = value[kFilenameInput].toString(); - LoopMode loop_mode = static_cast(value[kLoopModeInput].toInt()); - // If the file exists and the reference is valid, push a footage job to the renderer - if (QFileInfo(file).exists()) { + if (QFileInfo::exists(file)) { // Push length - table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, false, QStringLiteral("length")); + table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, QStringLiteral("length")); // Push each stream as a footage job for (int i=0; i= length; } -rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) +rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) { if (type == VideoParams::kVideoTypeStill) { // No looping for still images @@ -348,15 +336,15 @@ rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mod if (TimeIsOutOfBounds(time, length)) { switch (loop_mode) { - case kLoopModeOff: + case Decoder::kLoopModeOff: // Return no time to indicate no frame should be shown here time = rational::NaN; break; - case kLoopModeClamp: + case Decoder::kLoopModeClamp: // Clamp footage time to length time = clamp(time, rational(0), length - timebase); break; - case kLoopModeLoop: + case Decoder::kLoopModeLoop: // Loop footage time around job length do { if (time >= length) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 6b801972f..8397726a2 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -24,6 +24,7 @@ #include #include +#include "codec/decoder.h" #include "common/rational.h" #include "footagedescription.h" #include "node/output/viewer/viewer.h" @@ -44,12 +45,6 @@ class Footage : public ViewerOutput { Q_OBJECT public: - enum LoopMode { - kLoopModeOff, - kLoopModeLoop, - kLoopModeClamp - }; - /** * @brief Footage Constructor */ @@ -101,11 +96,6 @@ public: */ void SetValid(); - /** - * @brief Get currently set loop mode - */ - LoopMode loop_mode() const; - /** * @brief Return the current filename of this Footage object */ @@ -183,7 +173,7 @@ public: virtual Node *GetConnectedSampleOutput() override; - static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); + static rational AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); virtual void LoadFinishedEvent() override; @@ -191,7 +181,6 @@ public: virtual qint64 mod_time() const override; static const QString kFilenameInput; - static const QString kLoopModeInput; protected: virtual void InputValueChangedEvent(const QString &input, int element) override; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index a2a38a3be..5fcca82f5 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -21,6 +21,7 @@ #include "traverser.h" #include "node.h" +#include "node/block/clip/clip.h" #include "render/job/footagejob.h" #include "render/rendermanager.h" @@ -30,6 +31,12 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa { NodeValueDatabase database; + // HACK: Pick up loop mode from clips + Decoder::LoopMode old_loop_mode = loop_mode_; + if (const ClipBlock *clip = dynamic_cast(node)) { + loop_mode_ = clip->loop_mode(); + } + // We need to insert tables into the database for each input foreach (const QString& input, node->inputs()) { if (IsCancelled()) { @@ -39,6 +46,8 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa database.Insert(input, ProcessInput(node, input, range)); } + loop_mode_ = old_loop_mode; + return database; } @@ -260,7 +269,8 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu NodeTraverser::NodeTraverser() : cancel_(nullptr), - transform_(nullptr) + transform_(nullptr), + loop_mode_(Decoder::kLoopModeOff) { } @@ -436,7 +446,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) if (job.type() == Track::kVideo) { - rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), job.loop_mode(), job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); + rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), loop_mode_, job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); TexturePtr tex; diff --git a/app/node/traverser.h b/app/node/traverser.h index 1c0fbe9da..84f1b1606 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -149,6 +149,8 @@ protected: return block_stack_.empty() ? nullptr : block_stack_.back(); } + Decoder::LoopMode loop_mode() const { return loop_mode_; } + private: void PreProcessRow(const TimeRange &range, NodeValueRow &row); @@ -166,6 +168,8 @@ private: std::list block_stack_; + Decoder::LoopMode loop_mode_; + }; } diff --git a/app/render/job/footagejob.h b/app/render/job/footagejob.h index 6952d76b4..319be8a38 100644 --- a/app/render/job/footagejob.h +++ b/app/render/job/footagejob.h @@ -29,17 +29,15 @@ class FootageJob { public: FootageJob() : - type_(Track::kNone), - loop_mode_(Footage::kLoopModeOff) + type_(Track::kNone) { } - FootageJob(const QString& decoder, const QString& filename, Track::Type type, const rational& length, Footage::LoopMode loop_mode) : + FootageJob(const QString& decoder, const QString& filename, Track::Type type, const rational& length) : decoder_(decoder), filename_(filename), type_(type), - length_(length), - loop_mode_(loop_mode) + length_(length) { } @@ -98,16 +96,6 @@ public: length_ = length; } - Footage::LoopMode loop_mode() const - { - return loop_mode_; - } - - void set_loop_mode(Footage::LoopMode loop_mode) - { - loop_mode_ = loop_mode; - } - private: QString decoder_; @@ -123,8 +111,6 @@ private: rational length_; - Footage::LoopMode loop_mode_; - }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 400f0984c..c8617e095 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -503,7 +503,7 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination, input_time, audio_params, stream.cache_path(), - stream.loop_mode(), + loop_mode(), static_cast(ticket_->property("mode").toInt())); if (status == Decoder::kWaitingForConform) { diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 71e953c5a..b6f23d911 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -531,19 +531,46 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q // Draw zebra stripes and markers if (clip->connected_viewer()) { if (!clip->connected_viewer()->GetLength().isNull()) { + painter->setPen(shadow_color); + if (clip->media_in() < 0) { - // Draw stripes for sections of clip < 0 - qreal zebra_right = TimeToScene(-clip->media_in()); - if (zebra_right > GetTimelineLeftBound()) { - DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height)); + qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); + + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections of clip < 0 + if (zebra_right > GetTimelineLeftBound()) { + DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right - block_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_right; i>block_left; i-=TimeToScene(clip->connected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); + break; } } if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { - // Draw stripes for sections for clip > clip length qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); - if (zebra_left < GetTimelineRightBound()) { - DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections for clip > clip length + if (zebra_left < GetTimelineRightBound()) { + DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); + break; } } }