diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt
index 0e86913ad..b8b8e49a2 100644
--- a/app/audio/CMakeLists.txt
+++ b/app/audio/CMakeLists.txt
@@ -18,14 +18,14 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
audio/audiomanager.h
audio/audiomanager.cpp
+ audio/audiovisualwaveform.h
+ audio/audiovisualwaveform.cpp
audio/outputdeviceproxy.h
audio/outputdeviceproxy.cpp
audio/outputmanager.h
audio/outputmanager.cpp
audio/sampleformat.h
audio/sampleformat.cpp
- audio/sumsamples.h
- audio/sumsamples.cpp
audio/tempoprocessor.h
audio/tempoprocessor.cpp
PARENT_SCOPE
diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp
new file mode 100644
index 000000000..c9ed53f97
--- /dev/null
+++ b/app/audio/audiovisualwaveform.cpp
@@ -0,0 +1,300 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#include "audiovisualwaveform.h"
+
+#include
+
+#include "config/config.h"
+
+OLIVE_NAMESPACE_ENTER
+
+const int AudioVisualWaveform::kSumSampleRate = 200;
+
+void AudioVisualWaveform::AddSum(const float *samples, int nb_samples, int nb_channels)
+{
+ data_.append(SumSamples(samples, nb_samples, nb_channels));
+}
+
+/*
+void AudioVisualWaveform::AddSamples(SampleBufferPtr samples)
+{
+ if (!params_.is_valid()) {
+
+ }
+
+ int chunk_size = (audio_params_.sample_rate() / waveform_params.sample_rate());
+
+ qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
+ qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
+ qint64 end_offset = start_offset + length_offset;
+
+ if (wave_file.size() < end_offset) {
+ wave_file.resize(end_offset);
+ }
+
+ wave_file.seek(start_offset);
+
+ for (int i=0;isample_count();i+=chunk_size) {
+ QVector summary = SumSamples(samples,
+ i,
+ qMin(chunk_size, samples->sample_count_per_channel() - i));
+
+ wave_file.write(reinterpret_cast(summary.constData()),
+ summary.size() * sizeof(SampleSummer::Sum));
+ }
+}
+*/
+
+void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start)
+{
+ if (!channels_) {
+ qWarning() << "Failed to write samples - channel count is zero";
+ }
+
+ int start_index = channels_ * qFloor(kSumSampleRate * start.toDouble());
+ int samples_length = channels_ * qFloor(kSumSampleRate * (static_cast(samples->sample_count()) / static_cast(sample_rate)));
+
+ int end_index = start_index + samples_length;
+ if (data_.size() < end_index) {
+ data_.resize(end_index);
+ }
+
+ int chunk_size = sample_rate / kSumSampleRate;
+
+ for (int i=0; i summary = SumSamples(samples,
+ src_index,
+ qMin(chunk_size, samples->sample_count() - src_index));
+
+ memcpy(&data_.data()[i + start_index],
+ summary.constData(),
+ summary.size() * sizeof(SamplePerChannel));
+ }
+}
+
+AudioVisualWaveform AudioVisualWaveform::Cut(const rational &time)
+{
+ int sample_index = time_to_samples(time);
+
+ // Create a copy of this waveform chop the early section off
+ AudioVisualWaveform copy = *this;
+ copy.data_ = data_.mid(sample_index);
+
+ // Chop the latter section off too
+ data_.resize(sample_index);
+
+ return copy;
+}
+
+void AudioVisualWaveform::Append(const AudioVisualWaveform &waveform)
+{
+ data_.append(waveform.data_);
+}
+
+void AudioVisualWaveform::TrimIn(const rational &time)
+{
+ data_ = data_.mid(time_to_samples(time));
+}
+
+void AudioVisualWaveform::TrimOut(const rational &time)
+{
+ data_.resize(data_.size() - time_to_samples(time));
+}
+
+void AudioVisualWaveform::PrependSilence(const rational &time)
+{
+ int added_samples = time_to_samples(time);
+
+ // Resize buffer for extra space
+ data_.resize(data_.size() + added_samples);
+
+ // Shift all data forward
+ for (int i=data_.size()-1; i>=added_samples; i--) {
+ data_[i] = data_[i - added_samples];
+ }
+
+ // Fill remainder with silence
+ for (int i=0;i AudioVisualWaveform::SumSamples(const float *samples, int nb_samples, int nb_channels)
+{
+ return SumSamplesInternal(samples, nb_samples, nb_channels);
+}
+
+QVector AudioVisualWaveform::SumSamples(const qfloat16 *samples, int nb_samples, int nb_channels)
+{
+ return SumSamplesInternal(samples, nb_samples, nb_channels);
+}
+
+QVector AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
+{
+ QVector summed_samples(samples->audio_params().channel_count());
+
+ int end_index = start_index + length;
+
+ for (int i=start_index;iaudio_params().channel_count();channel++) {
+ ClampMinMax(summed_samples[channel], samples->data()[channel][i]);
+ }
+ }
+
+ return summed_samples;
+}
+
+QVector AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples,
+ int nb_samples,
+ int nb_channels)
+{
+ QVector summed_samples(nb_channels);
+
+ for (int i=0;i summed_samples[channel].max) {
+ summed_samples[channel].max = sample.max;
+ }
+ }
+
+ return summed_samples;
+}
+
+void AudioVisualWaveform::DrawSample(QPainter *painter, const QVector& sample, int x, int y, int height)
+{
+ int channel_height = height / sample.size();
+ int channel_half_height = channel_height / 2;
+
+ for (int i=0;idrawLine(x,
+ channel_bottom - diff,
+ x,
+ channel_bottom);
+ } else {
+ int channel_mid = y + channel_height * i + channel_half_height;
+
+ painter->drawLine(x,
+ channel_mid + qRound(sample.at(i).min * static_cast(channel_half_height)),
+ x,
+ channel_mid + qRound(sample.at(i).max * static_cast(channel_half_height)));
+ }
+ }
+}
+
+void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples)
+{
+ int sample_index, next_sample_index = 0;
+
+ QVector summary;
+ int summary_index = -1;
+
+ const QRect& viewport = painter->viewport();
+ QPoint top_left = painter->transform().map(viewport.topLeft());
+
+ int start = qMax(rect.x(), -top_left.x());
+ int end = qMin(rect.right(), -top_left.x() + viewport.width());
+
+ QVector lines;
+
+ for (int i=start;i(kSumSampleRate) * static_cast(i - rect.x() + 1) / scale) * samples.channel_count());
+
+ if (summary_index != sample_index) {
+ summary = AudioVisualWaveform::ReSumSamples(&samples.data_.at(sample_index),
+ qMax(samples.channel_count(), next_sample_index - sample_index),
+ samples.channel_count());
+ summary_index = sample_index;
+ }
+
+ DrawSample(painter, summary, i, rect.y(), rect.height());
+ }
+
+ painter->drawLines(lines);
+}
+
+int AudioVisualWaveform::time_to_samples(const rational &time) const
+{
+ return qFloor(time.toDouble() * kSumSampleRate) * channels_;
+}
+
+template
+QVector AudioVisualWaveform::SumSamplesInternal(const T *samples, int nb_samples, int nb_channels)
+{
+ QVector summed_samples(nb_channels);
+
+ for (int i=0;i(summed_samples[i%nb_channels], samples[i]);
+ }
+
+ return summed_samples;
+}
+
+template
+void AudioVisualWaveform::ClampMinMax(AudioVisualWaveform::SamplePerChannel &sum, T value)
+{
+ if (value < sum.min) {
+ sum.min = value;
+ }
+
+ if (value > sum.max) {
+ sum.max = value;
+ }
+}
+
+OLIVE_NAMESPACE_EXIT
diff --git a/app/audio/audiovisualwaveform.h b/app/audio/audiovisualwaveform.h
new file mode 100644
index 000000000..35df5a054
--- /dev/null
+++ b/app/audio/audiovisualwaveform.h
@@ -0,0 +1,110 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#ifndef SUMSAMPLES_H
+#define SUMSAMPLES_H
+
+#include
+#include
+#include
+
+#include "codec/samplebuffer.h"
+
+OLIVE_NAMESPACE_ENTER
+
+/**
+ * @brief A buffer of data used to store a visual representation of audio
+ *
+ * This differs from a SampleBuffer as the data in an AudioVisualWaveform has been reduced
+ * significantly and optimized for visual display.
+ */
+class AudioVisualWaveform {
+public:
+ AudioVisualWaveform() = default;
+
+ struct SamplePerChannel {
+ qfloat16 min = 0;
+ qfloat16 max = 0;
+ };
+
+ using Sample = QVector;
+
+ int channel_count() const
+ {
+ return channels_;
+ }
+
+ void set_channel_count(int channels)
+ {
+ channels_ = channels;
+ }
+
+ int nb_samples() const
+ {
+ return data_.size();
+ }
+
+ const SamplePerChannel* const_data() const
+ {
+ return data_.constData();
+ }
+
+ void AddSum(const float* samples, int nb_samples, int nb_channels);
+
+ void OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational& start);
+
+ AudioVisualWaveform Cut(const rational& time);
+ void Append(const AudioVisualWaveform& waveform);
+ void TrimIn(const rational& time);
+ void TrimOut(const rational& time);
+ void PrependSilence(const rational& time);
+ void AppendSilence(const rational& time);
+
+ // FIXME: Move to dynamic
+ static const int kSumSampleRate;
+
+ static QVector SumSamples(const float* samples, int nb_samples, int nb_channels);
+ static QVector SumSamples(const qfloat16* samples, int nb_samples, int nb_channels);
+ static QVector SumSamples(SampleBufferPtr samples, int start_index, int length);
+
+ static QVector ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels);
+
+ static void DrawSample(QPainter* painter, const QVector &sample, int x, int y, int height);
+
+ static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples);
+
+private:
+ template
+ static QVector SumSamplesInternal(const T* samples, int nb_samples, int nb_channels);
+
+ template
+ static void ClampMinMax(SamplePerChannel &sum, T value);
+
+ int time_to_samples(const rational& time) const;
+
+ int channels_ = 0;
+
+ QVector data_;
+
+};
+
+OLIVE_NAMESPACE_EXIT
+
+#endif // SUMSAMPLES_H
diff --git a/app/audio/sumsamples.cpp b/app/audio/sumsamples.cpp
deleted file mode 100644
index 3aa5db702..000000000
--- a/app/audio/sumsamples.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2019 Olive Team
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-***/
-
-#include "sumsamples.h"
-
-#include
-
-OLIVE_NAMESPACE_ENTER
-
-const int SampleSummer::kSumSampleRate = 200;
-
-QVector SampleSummer::SumSamples(const float *samples, int nb_samples, int nb_channels)
-{
- return SumSamplesInternal(samples, nb_samples, nb_channels);
-}
-
-QVector SampleSummer::SumSamples(const qfloat16 *samples, int nb_samples, int nb_channels)
-{
- return SumSamplesInternal(samples, nb_samples, nb_channels);
-}
-
-QVector SampleSummer::SumSamples(SampleBufferPtr samples, int start_index, int length)
-{
- QVector summed_samples(samples->audio_params().channel_count());
-
- int end_index = start_index + length;
-
- for (int i=start_index;iaudio_params().channel_count();channel++) {
- ClampMinMax(summed_samples[channel], samples->data()[channel][i]);
- }
- }
-
- return summed_samples;
-}
-
-QVector SampleSummer::ReSumSamples(const SampleSummer::Sum *samples, int nb_samples, int nb_channels)
-{
- QVector summed_samples(nb_channels);
-
- for (int i=0;i summed_samples[channel].max) {
- summed_samples[channel].max = sample.max;
- }
- }
-
- return summed_samples;
-}
-
-SampleSummer::Sum::Sum()
-{
- min = 0;
- max = 0;
-}
-
-template
-QVector SampleSummer::SumSamplesInternal(const T *samples, int nb_samples, int nb_channels)
-{
- QVector summed_samples(nb_channels);
-
- for (int i=0;i(summed_samples[i%nb_channels], samples[i]);
- }
-
- return summed_samples;
-}
-
-template
-void SampleSummer::ClampMinMax(SampleSummer::Sum &sum, T value)
-{
- if (value < sum.min) {
- sum.min = value;
- }
-
- if (value > sum.max) {
- sum.max = value;
- }
-}
-
-SampleSummer::Info::Info()
-{
- channels = 0;
-}
-
-OLIVE_NAMESPACE_EXIT
diff --git a/app/audio/sumsamples.h b/app/audio/sumsamples.h
deleted file mode 100644
index 00d8b4e92..000000000
--- a/app/audio/sumsamples.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2019 Olive Team
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-***/
-
-#ifndef SUMSAMPLES_H
-#define SUMSAMPLES_H
-
-#include
-#include
-
-#include "codec/samplebuffer.h"
-
-OLIVE_NAMESPACE_ENTER
-
-class SampleSummer {
-public:
- struct Sum {
- Sum();
-
- qfloat16 min;
- qfloat16 max;
- };
-
- // FIXME: Move to config
- static const int kSumSampleRate;
-
- static QVector SumSamples(const float* samples, int nb_samples, int nb_channels);
- static QVector SumSamples(const qfloat16* samples, int nb_samples, int nb_channels);
- static QVector SumSamples(SampleBufferPtr samples, int start_index, int length);
-
- static QVector ReSumSamples(const SampleSummer::Sum* samples, int nb_samples, int nb_channels);
-
- struct Info {
- Info();
-
- int channels;
- };
-
-private:
- template
- static QVector SumSamplesInternal(const T* samples, int nb_samples, int nb_channels);
-
- template
- static void ClampMinMax(Sum &sum, T value);
-
-};
-
-OLIVE_NAMESPACE_EXIT
-
-#endif // SUMSAMPLES_H
diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp
index 1dde4fbae..0cc6c540c 100644
--- a/app/codec/samplebuffer.cpp
+++ b/app/codec/samplebuffer.cpp
@@ -1,4 +1,4 @@
-/***
+/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
@@ -43,7 +43,7 @@ SampleBufferPtr SampleBuffer::CreateAllocated(const AudioRenderingParams &audio_
SampleBufferPtr buffer = Create();
buffer->set_audio_params(audio_params);
- buffer->set_sample_count_per_channel(samples_per_channel);
+ buffer->set_sample_count(samples_per_channel);
buffer->allocate();
return buffer;
@@ -88,12 +88,12 @@ void SampleBuffer::set_audio_params(const AudioRenderingParams ¶ms)
audio_params_ = params;
}
-const int &SampleBuffer::sample_count_per_channel() const
+const int &SampleBuffer::sample_count() const
{
return sample_count_per_channel_;
}
-void SampleBuffer::set_sample_count_per_channel(const int &sample_count)
+void SampleBuffer::set_sample_count(const int &sample_count)
{
if (data_) {
qWarning() << "Tried to set sample count on allocated sample buffer";
diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h
index 83a2e4d9c..33bcd07f4 100644
--- a/app/codec/samplebuffer.h
+++ b/app/codec/samplebuffer.h
@@ -54,8 +54,8 @@ public:
const AudioRenderingParams& audio_params() const;
void set_audio_params(const AudioRenderingParams& params);
- const int &sample_count_per_channel() const;
- void set_sample_count_per_channel(const int &sample_count_per_channel);
+ const int &sample_count() const;
+ void set_sample_count(const int &sample_count);
float** data();
const float** const_data() const;
diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt
index be51fa1e8..9b6a1e6f5 100644
--- a/app/common/CMakeLists.txt
+++ b/app/common/CMakeLists.txt
@@ -42,7 +42,6 @@ set(OLIVE_SOURCES
common/threadedobject.cpp
common/timecodefunctions.h
common/timecodefunctions.cpp
- common/timelinecommon.h
common/timerange.h
common/timerange.cpp
common/tohex.h
diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp
index 413050d98..288cb53c6 100644
--- a/app/node/block/clip/clip.cpp
+++ b/app/node/block/clip/clip.cpp
@@ -62,7 +62,7 @@ NodeInput *ClipBlock::texture_input() const
void ClipBlock::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source)
{
// If signal is from texture input, transform all times from media time to sequence time
- if (from == texture_input_) {
+ if (from == texture_input_ || from == media_in_input()) {
rational start = MediaToSequenceTime(range.in());
rational end = MediaToSequenceTime(range.out());
diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h
index 375f319ce..3ce6a7b39 100644
--- a/app/node/block/clip/clip.h
+++ b/app/node/block/clip/clip.h
@@ -21,6 +21,7 @@
#ifndef CLIPBLOCK_H
#define CLIPBLOCK_H
+#include "audio/audiovisualwaveform.h"
#include "node/block/block.h"
OLIVE_NAMESPACE_ENTER
@@ -56,12 +57,26 @@ public:
virtual void Hash(QCryptographicHash &hash, const rational &time) const override;
+ AudioVisualWaveform& waveform()
+ {
+ return waveform_;
+ }
+
+ void set_waveform(const AudioVisualWaveform& wave)
+ {
+ waveform_ = wave;
+
+ emit PreviewUpdated();
+ }
+
signals:
void PreviewUpdated();
private:
NodeInput* texture_input_;
+ AudioVisualWaveform waveform_;
+
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp
index 149bcb08a..f8d90e0db 100644
--- a/app/node/math/math/math.cpp
+++ b/app/node/math/math/math.cpp
@@ -318,8 +318,8 @@ NodeValueTable MathNode::Value(NodeValueDatabase &value) const
SampleBufferPtr samples_a = val_a.data().value();
SampleBufferPtr samples_b = val_b.data().value();
- int max_samples = qMax(samples_a->sample_count_per_channel(), samples_b->sample_count_per_channel());
- int min_samples = qMin(samples_a->sample_count_per_channel(), samples_b->sample_count_per_channel());
+ int max_samples = qMax(samples_a->sample_count(), samples_b->sample_count());
+ int min_samples = qMin(samples_a->sample_count(), samples_b->sample_count());
SampleBufferPtr mixed_samples = SampleBuffer::CreateAllocated(samples_a->audio_params(), max_samples);
diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h
index 1cb04f451..90d716b56 100644
--- a/app/node/output/track/track.h
+++ b/app/node/output/track/track.h
@@ -21,8 +21,8 @@
#ifndef TRACKOUTPUT_H
#define TRACKOUTPUT_H
-#include "common/timelinecommon.h"
#include "node/block/block.h"
+#include "timeline/timelinecommon.h"
OLIVE_NAMESPACE_ENTER
diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h
index 44c380cc9..d0923f577 100644
--- a/app/node/output/track/tracklist.h
+++ b/app/node/output/track/tracklist.h
@@ -23,9 +23,9 @@
#include
-#include "common/timelinecommon.h"
#include "node/graph.h"
#include "node/output/track/track.h"
+#include "timeline/timelinecommon.h"
OLIVE_NAMESPACE_ENTER
diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h
index 2b188e5da..31c9dab8d 100644
--- a/app/node/output/viewer/viewer.h
+++ b/app/node/output/viewer/viewer.h
@@ -23,7 +23,6 @@
#include
-#include "common/timelinecommon.h"
#include "node/block/block.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
@@ -32,6 +31,7 @@
#include "render/audioplaybackcache.h"
#include "render/framehashcache.h"
#include "render/videoparams.h"
+#include "timeline/timelinecommon.h"
#include "timeline/trackreference.h"
OLIVE_NAMESPACE_ENTER
diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp
index a022a1f37..5e465af50 100644
--- a/app/render/backend/renderworker.cpp
+++ b/app/render/backend/renderworker.cpp
@@ -22,7 +22,7 @@
#include
-#include "audio/sumsamples.h"
+#include "audio/audiovisualwaveform.h"
#include "common/functiontimer.h"
#include "config/config.h"
#include "node/block/clip/clip.h"
@@ -118,61 +118,24 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const
samples_from_this_block->reverse();
}
- int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel());
+ int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
- {
+ if (b->type() == Block::kClip) {
// Save waveform to file
- Block* src_block = static_cast(copy_map_->key(b));
- QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
- QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
- waveform_loc.mkpath(".");
- QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast(src_block))));
- QFile wave_file(wave_fn);
+ ClipBlock* src_block = static_cast(copy_map_->key(b));
- if (wave_file.open(QFile::ReadWrite)) {
- // We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in
- // size
- AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate,
- audio_params_.channel_layout(),
- SampleFormat::SAMPLE_FMT_S32);
+ AudioVisualWaveform& clip_waveform = src_block->waveform();
- int chunk_size = (audio_params_.sample_rate() / waveform_params.sample_rate());
+ clip_waveform.set_channel_count(audio_params_.channel_count());
- {
- // Write metadata header
- SampleSummer::Info info;
- info.channels = audio_params_.channel_count();
- wave_file.write(reinterpret_cast(&info), sizeof(SampleSummer::Info));
- }
+ clip_waveform.OverwriteSamples(samples_from_this_block,
+ audio_params_.sample_rate(),
+ range_for_block.in() - b->in());
- qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
- qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
- qint64 end_offset = start_offset + length_offset;
-
- if (wave_file.size() < end_offset) {
- wave_file.resize(end_offset);
- }
-
- wave_file.seek(start_offset);
-
- for (int i=0;isample_count_per_channel();i+=chunk_size) {
- QVector summary = SampleSummer::SumSamples(samples_from_this_block,
- i,
- qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i));
-
- wave_file.write(reinterpret_cast(summary.constData()),
- summary.size() * sizeof(SampleSummer::Sum));
- }
-
- wave_file.close();
-
- if (src_block->type() == Block::kClip) {
- emit static_cast(src_block)->PreviewUpdated();
- }
- }
+ emit static_cast(src_block)->PreviewUpdated();
}
NodeValueTable::Merge({merged_table, table});
@@ -212,9 +175,9 @@ void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, No
return;
}
- SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(input_buffer->audio_params(), input_buffer->sample_count_per_channel());
+ SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(input_buffer->audio_params(), input_buffer->sample_count());
- int sample_count = input_buffer->sample_count_per_channel();
+ int sample_count = input_buffer->sample_count();
// FIXME: Hardcoded float sample format
for (int i=0;i
-#include "common/timelinecommon.h"
#include "node/output/viewer/viewer.h"
+#include "timeline/timelinecommon.h"
#include "widget/resizablescrollbar/resizablescrollbar.h"
#include "widget/timelinewidget/timelinescaledobject.h"
#include "widget/timelinewidget/view/timelineview.h"
diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp
index f79cf65c5..48fd1ba61 100644
--- a/app/widget/timelinewidget/timelinewidget.cpp
+++ b/app/widget/timelinewidget/timelinewidget.cpp
@@ -473,7 +473,7 @@ void TimelineWidget::DeleteSelectedInternal(const QList &blocks,
foreach (Block* b, blocks) {
TrackOutput* original_track = TrackOutput::TrackFromBlock(b);
- if (transition_aware && b->type() == Block::kTransition) {
+ /*if (transition_aware && b->type() == Block::kTransition) {
// Deleting transitions restores their in/out offsets to their attached blocks
TransitionBlock* transition = static_cast(b);
@@ -494,20 +494,39 @@ void TimelineWidget::DeleteSelectedInternal(const QList &blocks,
transition->connected_out_block()->length() + transition->out_offset(),
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());
+ } else */
- new NodeAddCommand(static_cast(b->parent()),
- gap,
- command);
- new TrackReplaceBlockCommand(original_track,
- b,
- gap,
- command);
+ /*
+ 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) {
new BlockUnlinkAllCommand(b, command);
@@ -543,12 +562,14 @@ void TimelineWidget::DeleteSelected(bool ripple)
// Replace blocks with gaps (effectively deleting them)
DeleteSelectedInternal(blocks_to_delete, true, true, command);
+ /*
// Clean each track
foreach (const TrackReference& track, tracks_affected) {
new TrackCleanGapsCommand(GetConnectedNode()->track_list(track.type()),
track.index(),
command);
}
+ */
// Insert ripple command now that it's all cleaned up gaps
if (ripple) {
@@ -1225,17 +1246,17 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected)
}
}
-QVector TimelineWidget::GetEditToInfo(const rational& playhead_time,
+QVector TimelineWidget::GetEditToInfo(const rational& playhead_time,
Timeline::MovementMode mode)
{
// Get list of unlocked tracks
QVector tracks = GetConnectedNode()->GetUnlockedTracks();
// Create list to cache nearest times and the blocks at this point
- QVector info_list(tracks.size());
+ QVector info_list(tracks.size());
for (int i=0;i tracks = GetEditToInfo(playhead_time, mode);
+ QVector tracks = GetEditToInfo(playhead_time, mode);
// Find each track's nearest point and determine the overall timeline's nearest point
rational closest_point_to_playhead = (mode == Timeline::kTrimIn) ? rational() : RATIONAL_MAX;
- foreach (const EditToInfo& info, tracks) {
+ foreach (const Timeline::EditToInfo& info, tracks) {
if (info.nearest_block) {
if (mode == Timeline::kTrimIn) {
closest_point_to_playhead = qMax(info.nearest_time, closest_point_to_playhead);
@@ -1306,7 +1327,7 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode)
QUndoCommand* command = new QUndoCommand();
- foreach (const EditToInfo& info, tracks) {
+ foreach (const Timeline::EditToInfo& info, tracks) {
TrackOutput* track = info.track;
// Simply remove this region
@@ -1335,63 +1356,32 @@ void TimelineWidget::EditTo(Timeline::MovementMode mode)
const rational playhead_time = GetTime();
// Get list of unlocked tracks
- QVector tracks = GetEditToInfo(playhead_time, mode);
+ QVector tracks = GetEditToInfo(playhead_time, mode);
+
+ if (tracks.isEmpty()) {
+ return;
+ }
QUndoCommand* command = new QUndoCommand();
- foreach (const EditToInfo& info, tracks) {
- TrackOutput* track = info.track;
+ foreach (const Timeline::EditToInfo& info, tracks) {
+ if (info.nearest_block
+ && info.nearest_block->type() != Block::kGap
+ && info.nearest_time != playhead_time) {
+ rational new_len;
- Block* block_here = info.nearest_block;
-
- // Check if this track's nearest time was the playhead or if there's no block here, in which
- // case this is a no-op
- if (!block_here || info.nearest_time == playhead_time) {
- continue;
- }
-
- GapBlock* gap = nullptr;
-
- // Resize the block at this time
- if (mode == Timeline::kTrimIn) {
- rational trim_length = playhead_time - block_here->in();
-
- gap = new GapBlock();
- gap->set_length_and_media_out(trim_length);
- new NodeAddCommand(static_cast(track->parent()), gap, command);
-
- new BlockResizeWithMediaInCommand(block_here,
- block_here->length() - trim_length,
- command);
-
- if (block_here->previous()) {
- new TrackInsertBlockAfterCommand(track, gap, block_here->previous(), command);
+ if (mode == Timeline::kTrimIn) {
+ new_len = playhead_time - info.nearest_time;
} else {
- new TrackPrependBlockCommand(track, gap, command);
+ new_len = info.nearest_time - playhead_time;
}
- } else {
- rational trim_length = block_here->out() - playhead_time;
+ new_len = info.nearest_block->length() - new_len;
- new BlockResizeCommand(block_here,
- block_here->length() - trim_length,
- command);
-
- // We only need to add a gap if there's actually a block after this one, otherwise it
- // doesn't matter
- if (block_here->next()) {
- gap = new GapBlock();
- gap->set_length_and_media_out(trim_length);
- new NodeAddCommand(static_cast(track->parent()), gap, command);
-
- new TrackInsertBlockAfterCommand(track, gap, block_here, command);
- }
- }
-
- // If a gap was added, clean the gaps on this track too
- if (gap) {
- new TrackCleanGapsCommand(GetConnectedNode()->track_list(track->track_type()),
- track->Index(),
- command);
+ new BlockTrimCommand(info.track,
+ info.nearest_block,
+ new_len,
+ mode,
+ command);
}
}
diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h
index 89094d80f..4c9015503 100644
--- a/app/widget/timelinewidget/timelinewidget.h
+++ b/app/widget/timelinewidget/timelinewidget.h
@@ -26,8 +26,9 @@
#include
#include "core.h"
-#include "timelineandtrackview.h"
#include "node/output/viewer/viewer.h"
+#include "timeline/timelinecommon.h"
+#include "timelineandtrackview.h"
#include "widget/nodecopypaste/nodecopypaste.h"
#include "widget/slider/timeslider.h"
#include "widget/timebased/timebased.h"
@@ -179,7 +180,7 @@ 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 ValidateFrameMovement(rational movement, const QVector ghosts);
+ rational ValidateTimeMovement(rational movement, const QVector ghosts);
/**
* @brief Validates Ghosts that are moving vertically (track-based)
@@ -226,15 +227,11 @@ private:
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
- void SetMovementAllowed(bool allowed);
- void SetTrackMovementAllowed(bool allowed);
- void SetTrimmingAllowed(bool allowed);
- virtual void MouseReleaseInternal(TimelineViewMouseEvent *event);
- virtual rational FrameValidateInternal(rational time_movement, const QVector &ghosts);
+ virtual void FinishDrag(TimelineViewMouseEvent *event);
- virtual void InitiateGhosts(TimelineViewBlockItem* clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming);
+ virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming);
TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode);
@@ -258,24 +255,51 @@ private:
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos);
+ const Timeline::MovementMode& drag_movement_mode() const
+ {
+ return drag_movement_mode_;
+ }
+
+ void SetMovementAllowed(bool e)
+ {
+ movement_allowed_ = e;
+ }
+
+ void SetTrimmingAllowed(bool e)
+ {
+ trimming_allowed_ = e;
+ }
+
+ void SetTrackMovementAllowed(bool e)
+ {
+ track_movement_allowed_ = e;
+ }
+
+ void SetTrimOverwriteAllowed(bool e)
+ {
+ trim_overwrite_allowed_ = e;
+ }
+
private:
Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x);
- void InitiateDrag(TimelineViewMouseEvent *mouse_pos);
-
void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode);
bool IsClipTrimmable(TimelineViewBlockItem* clip,
const QList& items,
const Timeline::MovementMode& mode);
- TrackReference track_start_;
bool movement_allowed_;
bool trimming_allowed_;
bool track_movement_allowed_;
+ bool trim_overwrite_allowed_;
bool rubberband_selecting_;
Timeline::TrackType drag_track_type_;
+ Timeline::MovementMode drag_movement_mode_;
+
+ TimelineViewBlockItem* clicked_item_;
+
};
class ImportTool : public Tool
@@ -332,12 +356,11 @@ private:
public:
RippleTool(TimelineWidget* parent);
protected:
- virtual void MouseReleaseInternal(TimelineViewMouseEvent *event) override;
- virtual rational FrameValidateInternal(rational time_movement, const QVector& ghosts) override;
+ virtual void FinishDrag(TimelineViewMouseEvent *event) override;
- virtual void InitiateGhosts(TimelineViewBlockItem* clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming) override;
+ virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming) override;
};
class RollingTool : public PointerTool
@@ -346,12 +369,11 @@ private:
RollingTool(TimelineWidget* parent);
protected:
- virtual void MouseReleaseInternal(TimelineViewMouseEvent *event) override;
- virtual rational FrameValidateInternal(rational time_movement, const QVector& ghosts) override;
+ virtual void FinishDrag(TimelineViewMouseEvent *event) override;
- virtual void InitiateGhosts(TimelineViewBlockItem* clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming) override;
+ virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming) override;
};
class SlideTool : public PointerTool
@@ -360,11 +382,11 @@ private:
SlideTool(TimelineWidget* parent);
protected:
- virtual void MouseReleaseInternal(TimelineViewMouseEvent *event) override;
- virtual rational FrameValidateInternal(rational time_movement, const QVector& ghosts) override;
- virtual void InitiateGhosts(TimelineViewBlockItem* clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming) override;
+ virtual void FinishDrag(TimelineViewMouseEvent *event) override;
+ virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming) override;
+
};
class SlipTool : public PointerTool
@@ -374,7 +396,7 @@ private:
protected:
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override;
- virtual void MouseReleaseInternal(TimelineViewMouseEvent *event) override;
+ virtual void FinishDrag(TimelineViewMouseEvent *event) override;
};
class ZoomTool : public Tool
@@ -425,13 +447,7 @@ private:
void SetBlockLinksSelected(Block *block, bool selected);
- struct EditToInfo {
- TrackOutput* track;
- rational nearest_time;
- Block* nearest_block;
- };
-
- QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
+ QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
void RippleTo(Timeline::MovementMode mode);
diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp
index ce47e2888..4d16ea080 100644
--- a/app/widget/timelinewidget/tool/add.cpp
+++ b/app/widget/timelinewidget/tool/add.cpp
@@ -37,7 +37,7 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event)
const TrackReference& track = event->GetTrack();
TrackOutput* t = parent()->GetTrackFromReference(track);
- if (t && t->IsLocked()) {
+ if (!t || t->IsLocked()) {
return;
}
diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp
index 1709059d9..c48593326 100644
--- a/app/widget/timelinewidget/tool/import.cpp
+++ b/app/widget/timelinewidget/tool/import.cpp
@@ -127,7 +127,7 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
SnapPoint(snap_points_, &time_movement);
}
- time_movement = ValidateFrameMovement(time_movement, parent()->ghost_items_);
+ time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
rational earliest_ghost = RATIONAL_MAX;
@@ -236,11 +236,14 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
footage_duration = Config::Current()["DefaultStillLength"].value();
} else {
// Rescale stream duration to timeline timebase
- int64_t stream_duration = Timecode::rescale_timestamp_ceil(stream->duration(), stream->timebase(), dest_tb);
-
// Convert to rational time
- footage_duration = rational(dest_tb.numerator() * stream_duration,
- dest_tb.denominator());
+ if (footage.footage()->workarea()->enabled()) {
+ footage_duration = footage.footage()->workarea()->range().length();
+ ghost->SetMediaIn(footage.footage()->workarea()->in());
+ } else {
+ int64_t stream_duration = Timecode::rescale_timestamp_ceil(stream->duration(), stream->timebase(), dest_tb);
+ footage_duration = Timecode::timestamp_to_time(stream_duration, dest_tb);
+ }
}
ghost->SetIn(ghost_start);
@@ -387,6 +390,7 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert)
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value();
ClipBlock* clip = new ClipBlock();
+ clip->set_media_in(ghost->MediaIn());
clip->set_length_and_media_out(ghost->Length());
clip->set_block_name(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp
index 31d17bb65..39c20e006 100644
--- a/app/widget/timelinewidget/tool/pointer.cpp
+++ b/app/widget/timelinewidget/tool/pointer.cpp
@@ -41,41 +41,39 @@ TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) :
movement_allowed_(true),
trimming_allowed_(true),
track_movement_allowed_(true),
+ trim_overwrite_allowed_(false),
rubberband_selecting_(false)
{
}
void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
{
- // Main selection code
+ // Determine if item clicked on is selectable
+ clicked_item_ = GetItemAtScenePos(event->GetCoordinates());
- TimelineViewBlockItem* item = GetItemAtScenePos(event->GetCoordinates());
-
- bool selectable_item = (item != nullptr
- && item->flags() & QGraphicsItem::ItemIsSelectable
- && !parent()->GetTrackFromReference(item->Track())->IsLocked());
+ bool selectable_item = (clicked_item_
+ && clicked_item_->flags() & QGraphicsItem::ItemIsSelectable
+ && !parent()->GetTrackFromReference(clicked_item_->Track())->IsLocked());
if (selectable_item) {
// Cache the clip's type for use later
- drag_track_type_ = item->Track().type();
- }
+ drag_track_type_ = clicked_item_->Track().type();
- // If this item is already selected
- if (selectable_item
- && item->isSelected()) {
+ // If this item is already selected, no further selection needs to be made
+ if (clicked_item_->isSelected()) {
- // If shift is held, deselect it
- if (event->GetModifiers() & Qt::ShiftModifier) {
- item->setSelected(false);
+ // If shift is held, deselect it
+ if (event->GetModifiers() & Qt::ShiftModifier) {
+ clicked_item_->setSelected(false);
- // If not holding alt, deselect all links as well
- if (!(event->GetModifiers() & Qt::AltModifier)) {
- parent()->SetBlockLinksSelected(item->block(), false);
+ // If not holding alt, deselect all links as well
+ if (!(event->GetModifiers() & Qt::AltModifier)) {
+ parent()->SetBlockLinksSelected(clicked_item_->block(), false);
+ }
}
- }
- // Otherwise do nothing
- return;
+ return;
+ }
}
// If not holding shift, deselect all clips
@@ -85,11 +83,11 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
if (selectable_item) {
// Select this item
- item->setSelected(true);
+ clicked_item_->setSelected(true);
// If not holding alt, select all links as well
if (!(event->GetModifiers() & Qt::AltModifier)) {
- parent()->SetBlockLinksSelected(item->block(), true);
+ parent()->SetBlockLinksSelected(clicked_item_->block(), true);
}
} else if (event->GetButton() == Qt::LeftButton) {
// Start rubberband drag
@@ -110,7 +108,28 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event)
// Now that the cursor has moved, we will assume the intention is to drag
// If we haven't started dragging yet, we'll initiate a drag here
- InitiateDrag(event);
+ // Record where the drag started in timeline coordinates
+ drag_start_ = event->GetCoordinates();
+
+ // Clear snap points
+ snap_points_.clear();
+
+ // Determine whether we're trimming or moving based on the position of the cursor
+ drag_movement_mode_ = IsCursorInTrimHandle(clicked_item_,
+ event->GetSceneX());
+
+ // If we're not in a trim mode, we must be in a move mode (provided the tool allows movement and
+ // the block is not a gap)
+ if (drag_movement_mode_ == Timeline::kNone
+ && movement_allowed_
+ && clicked_item_->block()->type() != Block::kGap) {
+ drag_movement_mode_ = Timeline::kMove;
+ }
+
+ // If we're performing an action, we can initiate ghosts
+ if (drag_movement_mode_ != Timeline::kNone) {
+ InitiateDrag(clicked_item_, drag_movement_mode_, false);
+ }
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
dragging_ = true;
@@ -132,11 +151,11 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
return;
}
- if (!parent()->ghost_items_.isEmpty()) {
- MouseReleaseInternal(event);
- }
-
if (dragging_) {
+ if (!parent()->ghost_items_.isEmpty()) {
+ FinishDrag(event);
+ }
+
parent()->ClearGhosts();
snap_points_.clear();
}
@@ -146,18 +165,22 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event)
{
- // No dragging, but we still want to process cursors
- TimelineViewBlockItem* block_at_cursor = GetItemAtScenePos(event->GetCoordinates());
+ if (trimming_allowed_) {
+ // No dragging, but we still want to process cursors
+ TimelineViewBlockItem* block_at_cursor = GetItemAtScenePos(event->GetCoordinates());
- if (block_at_cursor) {
- switch (IsCursorInTrimHandle(block_at_cursor, event->GetSceneX())) {
- case Timeline::kTrimIn:
- parent()->setCursor(Qt::SizeHorCursor);
- break;
- case Timeline::kTrimOut:
- parent()->setCursor(Qt::SizeHorCursor);
- break;
- default:
+ if (block_at_cursor) {
+ switch (IsCursorInTrimHandle(block_at_cursor, event->GetSceneX())) {
+ case Timeline::kTrimIn:
+ parent()->setCursor(Qt::SizeHorCursor);
+ break;
+ case Timeline::kTrimOut:
+ parent()->setCursor(Qt::SizeHorCursor);
+ break;
+ default:
+ parent()->unsetCursor();
+ }
+ } else {
parent()->unsetCursor();
}
} else {
@@ -165,200 +188,123 @@ void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event)
}
}
-void TimelineWidget::PointerTool::SetMovementAllowed(bool allowed)
+void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
{
- movement_allowed_ = allowed;
-}
+ QList ghosts_moving;
+ QList blocks_moving;
+ QList ghosts_trimming;
+ QList blocks_trimming;
-void TimelineWidget::PointerTool::SetTrackMovementAllowed(bool allowed)
-{
- track_movement_allowed_ = allowed;
-}
+ foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
+ if (!ghost->HasBeenAdjusted()) {
+ continue;
+ }
-void TimelineWidget::PointerTool::SetTrimmingAllowed(bool allowed)
-{
- trimming_allowed_ = allowed;
-}
+ Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
+
+ 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);
-void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
-{
QUndoCommand* command = new QUndoCommand();
- QList blocks_to_temp_remove;
- QList tracks_affected;
- QList ignore_ghosts;
+ for (int i=0;iGetModifiers() & Qt::AltModifier);
-
- // Since all the ghosts will be leaving their old position in some way, we replace all of them with gaps here so the
- // entire timeline isn't disrupted in the process
- for (int i=0;ighost_items_.size();i++) {
- TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
-
- // If the ghost has not been adjusted nothing needs to be done
- if (!ghost->HasBeenAdjusted()) {
- ignore_ghosts.append(ghost);
- continue;
- }
-
- Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
-
- if (!duplicate_clips || ghost->mode() != Timeline::kMove || b->type() == Block::kTransition) {
- // If we're duplicating (user is holding ALT), no need to remove the original clip. However if the ghost was
- // trimmed, it can't be duplicated.
- blocks_to_temp_remove.append(b);
- }
-
- if (!tracks_affected.contains(ghost->Track())) {
- tracks_affected.append(ghost->Track());
- }
-
- if (!tracks_affected.contains(ghost->GetAdjustedTrack())) {
- tracks_affected.append(ghost->GetAdjustedTrack());
- }
- }
-
- bool inserting = (event->GetModifiers() & Qt::ControlModifier);
-
- // If there are any blocks to remove, remove them
- parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, false, command);
-
- if (inserting) {
- // Make room to insert clips to
- InsertGapsAtGhostDestination(parent()->ghost_items_, command);
- }
-
- // Now we place the clips back in the timeline where the user moved them. It's legal for them to overwrite parts or
- // all of the gaps we inserted earlier
- for (int i=0;ighost_items_.size();i++) {
- TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
-
- // If the ghost has not been adjusted nothing needs to be done
- if (ignore_ghosts.contains(ghost)) {
- continue;
- }
-
- const TrackReference& track_ref = ghost->GetAdjustedTrack();
- //TrackOutput* track = parent()->GetTrackFromReference(track_ref);
-
- Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
-
- // Normal blocks work in conjunction with the gap made above
- if (Timeline::IsATrimMode(ghost->mode())) {
- // If we were trimming, we'll need to change the length
-
- // If we were trimming the in point, we'll need to adjust the media in too
- if (ghost->mode() == Timeline::kTrimIn) {
- new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
- } else {
- new BlockResizeCommand(b, ghost->AdjustedLength(), command);
- }
- } else if (duplicate_clips && ghost->mode() == Timeline::kMove && b->type() != Block::kTransition) {
- // Duplicate rather than move
- Node* copy = b->copy();
-
- new NodeAddCommand(static_cast(b->parent()),
- copy,
+ new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()),
+ blocks_trimming.at(i),
+ ghost->AdjustedLength(),
+ ghost->mode(),
command);
+ }
- new NodeCopyInputsCommand(b, copy, true, command);
-
- // Place the copy instead of the original block
- b = static_cast(copy);
- } else if (b->type() == Block::kTransition) {
- // If the block is a dual transition and we're moving it, the mid point should be moved
- TransitionBlock* transition = static_cast(b);
-
- if (transition->connected_in_block() && transition->connected_out_block()) {
- new BlockSetMediaInCommand(transition,
- transition->media_in() + ghost->InAdjustment(),
- 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 (b->type() == Block::kTransition && ghost->AdjustedLength() == 0) {
- // Remove transitions that have been reduced to zero length
- new NodeRemoveCommand(static_cast(b->parent()),
- {b},
- command);
- } else {
- // Normal block placement
+ if (inserting) {
+ // If we're inserting, ripple everything at the destination with gaps
+ InsertGapsAtGhostDestination(parent()->ghost_items_, command);
+ }
+
+ /*
+ QList tracks_affected;
+ */
+
+ // Now we can re-add each clip
+ for (int i=0;iGetAdjustedTrack())) {
+ tracks_affected.append(ghost->GetAdjustedTrack());
+ }
+ */
+
+ 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);
+ /*
+ } else if (!tracks_affected.contains(ghost->Track())) {
+ // Block moved from its original position. Mark its track as affected.
+ tracks_affected.append(ghost->Track());
+ */
+ }
+
+ const TrackReference& track_ref = ghost->GetAdjustedTrack();
new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()),
track_ref.index(),
- b,
+ block,
ghost->GetAdjustedIn(),
command);
}
- }
- if (command->childCount() > 0) {
+ /*
foreach (const TrackReference& t, tracks_affected) {
new TrackCleanGapsCommand(parent()->GetConnectedNode()->track_list(t.type()),
t.index(),
command);
}
+ */
+
+ // FIXME: Heavy optimization since MOST of the timeline does NOT change in this time
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
-rational TimelineWidget::PointerTool::FrameValidateInternal(rational time_movement, const QVector& ghosts)
-{
- // Default behavior is to validate all movement and trimming
- time_movement = ValidateFrameMovement(time_movement, ghosts);
- time_movement = ValidateInTrimming(time_movement, ghosts, true);
- time_movement = ValidateOutTrimming(time_movement, ghosts, true);
-
- return time_movement;
-}
-
-void TimelineWidget::PointerTool::InitiateDrag(TimelineViewMouseEvent *mouse_pos)
-{
- // Record where the drag started in timeline coordinates
- drag_start_ = mouse_pos->GetCoordinates();
-
- // Get the item that was clicked
- TimelineViewBlockItem* clicked_item = GetItemAtScenePos(drag_start_);
-
- // We only initiate a pointer drag if the user actually dragged an item, otherwise if they dragged on empty space
- // QGraphicsView default behavior would initiate a rubberband drag
- if (clicked_item != nullptr) {
-
- // Clear snap points
- snap_points_.clear();
-
- // Record where the drag started in timeline coordinates
- track_start_ = mouse_pos->GetTrack();
-
- // Determine whether we're trimming or moving based on the position of the cursor
- Timeline::MovementMode trim_mode = IsCursorInTrimHandle(clicked_item, mouse_pos->GetSceneX());
-
- // Some derived classes don't allow movement
- if (trim_mode == Timeline::kNone && movement_allowed_) {
- trim_mode = Timeline::kMove;
- }
-
- // Gaps can't be moved, only trimmed
- if (clicked_item->block()->type() == Block::kGap && trim_mode == Timeline::kMove) {
- trim_mode = Timeline::kNone;
- }
-
- // Make sure we can actually perform an action here
- if (trim_mode != Timeline::kNone) {
- InitiateGhosts(clicked_item, trim_mode, false);
- }
- }
-}
-
void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
{
- // Determine track movement
- const TrackReference& cursor_track = mouse_pos.GetTrack();
- int track_movement = 0;
-
- if (track_movement_allowed_) {
- track_movement = cursor_track.index() - track_start_.index();
- }
+ // Calculate track movement
+ int track_movement = track_movement_allowed_
+ ? mouse_pos.GetTrack().index() - drag_start_.GetTrack().index()
+ : 0;
// Determine frame movement
rational time_movement = mouse_pos.GetFrame() - drag_start_.GetFrame();
@@ -369,17 +315,22 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
}
// Validate movement (enforce all ghosts moving in legal ways)
- time_movement = FrameValidateInternal(time_movement, parent()->ghost_items_);
+ // NOTE: Always do this after snapping to ensure the snap hasn't made an illegal 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_);
- // Validate ghosts that are being moved (clips from other track types do NOT get validated or moved)
- QVector validate_track_ghosts = parent()->ghost_items_;
- for (int i=0;iTrack().type() != drag_track_type_) {
- validate_track_ghosts.removeAt(i);
- i--;
+ // Validate ghosts that are being moved (clips from other track types do NOT get moved)
+ {
+ QVector validate_track_ghosts = parent()->ghost_items_;
+ for (int i=0;iTrack().type() != drag_track_type_) {
+ validate_track_ghosts.removeAt(i);
+ i--;
+ }
}
+ track_movement = ValidateTrackMovement(track_movement, validate_track_ghosts);
}
- track_movement = ValidateTrackMovement(track_movement, validate_track_ghosts);
// Perform movement
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
@@ -401,28 +352,23 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
// Also, we only move the clips on the same track type that the drag started from
if (ghost->Track().type() == drag_track_type_) {
ghost->SetTrackAdjustment(track_movement);
- }
- const TrackReference& track = ghost->GetAdjustedTrack();
- ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
+ const TrackReference& track = ghost->GetAdjustedTrack();
+ ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
+ }
break;
}
}
}
- // Show tooltip
- // Generate tooltip (showing earliest in point of imported clip)
- int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase());
- QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
- parent()->GetToolTipTimebase(),
- Core::instance()->GetTimecodeDisplay(),
- true);
-
- // Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
- // of the cursor)
+ // 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)
QToolTip::hideText();
QToolTip::showText(QCursor::pos(),
- tooltip_text,
+ Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase()),
+ parent()->GetToolTipTimebase(),
+ Core::instance()->GetTimecodeDisplay(),
+ true),
parent());
}
@@ -444,89 +390,61 @@ Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(Timelin
}
}
-void TimelineWidget::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming)
+void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming)
{
- // Convert selected items list to clips list
+ // Get list of selected blocks
QList clips = parent()->GetSelectedBlocks();
- // If trimming multiple clips, we only trim the earliest in each track (trimming in) or the latest in each track
- // (trimming out). If the current clip is NOT one of these, we only trim it.
- bool multitrim_enabled = true;
+ if (trim_mode == Timeline::kMove) {
- // Determine if the clicked item is the earliest/latest in the track for in/out trimming respectively
- if (Timeline::IsATrimMode(trim_mode)) {
- multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode);
- }
+ // Create ghosts for moving
+ foreach (TimelineViewBlockItem* clip_item, clips) {
- // For each selected item, create a "ghost", a visual representation of the action before it gets performed
- foreach (TimelineViewBlockItem* clip_item, clips) {
+ // Gaps are not allowed to move, so we ignore those here
+ if (clip_item->block()->type() == Block::kGap) {
+ continue;
+ }
- // Determine correct mode for ghost
- //
- // Movement is indiscriminate, all the ghosts can be set to do this, however trimming is limited to one block
- // PER TRACK
-
- bool include_this_clip = true;
-
- if (clip_item->block()->type() == Block::kGap
- && !Timeline::IsATrimMode(trim_mode)) {
- continue;
+ AddGhostFromBlock(clip_item->block(), clip_item->Track(), trim_mode);
}
- if (clip_item != clicked_item
- && (Timeline::IsATrimMode(trim_mode))) {
- include_this_clip = multitrim_enabled ? IsClipTrimmable(clip_item, clips, trim_mode) : false;
- }
+ } 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;
+ }
- if (include_this_clip) {
Block* block = clip_item->block();
Timeline::MovementMode block_mode = trim_mode;
- // If we don't allow gap trimming, we automatically switch to the next/previous block to trim
+ // 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 && !allow_gap_trimming) {
- if (trim_mode == Timeline::kTrimIn) {
- // Trim the previous clip's out point instead
- block = block->previous();
- } else {
- // Assume kTrimOut
- block = block->next();
- }
+ block = (trim_mode == Timeline::kTrimIn) ? block->previous() : block->next();
block_mode = FlipTrimMode(trim_mode);
- }
- if (block) {
- TimelineViewGhostItem* ghost = AddGhostFromBlock(block, clip_item->Track(), block_mode);
-
- if (block->type() == Block::kTransition) {
- TransitionBlock* transition = static_cast(block);
-
- bool transition_can_move_tracks = false;
-
- // Create a rolling effect with the attached block
- if (transition->connected_in_block()) {
- if (parent()->block_items_.value(transition->connected_in_block())->isSelected()) {
- // We'll be moving this item too, no need to create a ghost for it here
- transition_can_move_tracks = true;
- } else if (block_mode == Timeline::kTrimOut || block_mode == Timeline::kMove) {
- AddGhostFromBlock(transition->connected_in_block(), clip_item->Track(), Timeline::kTrimIn);
- }
- }
-
- if (transition->connected_out_block()) {
- if (parent()->block_items_.value(transition->connected_in_block())->isSelected()) {
- // We'll be moving this item too, no need to create a ghost for it here
- transition_can_move_tracks = true;
- } else if (block_mode == Timeline::kTrimIn || block_mode == Timeline::kMove) {
- AddGhostFromBlock(transition->connected_out_block(), clip_item->Track(), Timeline::kTrimOut);
- }
- }
-
- ghost->SetCanMoveTracks(transition_can_move_tracks);
+ // If there's no adjacent block, do nothing here
+ if (!block) {
+ continue;
}
}
+
+ // Create ghost for this block
+ AddGhostFromBlock(block, clip_item->Track(), block_mode);
}
+
}
}
diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp
index d6c3ff800..5c3efca98 100644
--- a/app/widget/timelinewidget/tool/ripple.cpp
+++ b/app/widget/timelinewidget/tool/ripple.cpp
@@ -29,74 +29,16 @@ TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
+ SetTrimOverwriteAllowed(true);
}
-void TimelineWidget::RippleTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
-{
- Q_UNUSED(event)
-
- // For ripple operations, all ghosts will be moving the same way
- Timeline::MovementMode movement_mode = parent()->ghost_items_.first()->mode();
-
- QUndoCommand* command = new QUndoCommand();
-
- // Find earliest point to ripple around
- foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
- Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
-
- if (b == nullptr) {
- // This is a gap we are creating
-
- // Make sure there's actually a gap being created
- if (ghost->AdjustedLength() > 0) {
- GapBlock* gap = new GapBlock();
- gap->set_length_and_media_out(ghost->AdjustedLength());
- new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), gap, command);
-
- Block* block_to_append_gap_to = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kReferenceBlock));
-
- new TrackInsertBlockAfterCommand(parent()->GetTrackFromReference(ghost->Track()),
- gap,
- block_to_append_gap_to,
- command);
- }
- } else {
- // This was a Block that already existed
- if (ghost->AdjustedLength() > 0) {
- if (movement_mode == Timeline::kTrimIn) {
- // We'll need to shift the media in point too
- new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
- } else {
- new BlockResizeCommand(b, ghost->AdjustedLength(), command);
- }
- } else {
- // Assumed the Block was a Gap and it was reduced to zero length, remove it here
- new TrackRippleRemoveBlockCommand(parent()->GetTrackFromReference(ghost->Track()), b, command);
-
- new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command);
- }
- }
- }
-
- Core::instance()->undo_stack()->pushIfHasChildren(command);
-}
-
-rational TimelineWidget::RippleTool::FrameValidateInternal(rational time_movement, const QVector &ghosts)
-{
- // Only validate trimming, and we don't care about "overwriting" since the ripple tool is nondestructive
- time_movement = ValidateInTrimming(time_movement, ghosts, false);
- time_movement = ValidateOutTrimming(time_movement, ghosts, false);
-
- return time_movement;
-}
-
-void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming)
+void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming)
{
Q_UNUSED(allow_gap_trimming)
- PointerTool::InitiateGhosts(clicked_item, trim_mode, true);
+ PointerTool::InitiateDrag(clicked_item, trim_mode, true);
if (parent()->ghost_items_.isEmpty()) {
return;
@@ -155,4 +97,54 @@ void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_i
}
}
+void TimelineWidget::RippleTool::FinishDrag(TimelineViewMouseEvent *event)
+{
+ Q_UNUSED(event)
+
+ // For ripple operations, all ghosts will be moving the same way
+ Timeline::MovementMode movement_mode = parent()->ghost_items_.first()->mode();
+
+ QUndoCommand* command = new QUndoCommand();
+
+ // Find earliest point to ripple around
+ foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
+ Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
+
+ if (b == nullptr) {
+ // This is a gap we are creating
+
+ // Make sure there's actually a gap being created
+ if (ghost->AdjustedLength() > 0) {
+ GapBlock* gap = new GapBlock();
+ gap->set_length_and_media_out(ghost->AdjustedLength());
+ new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), gap, command);
+
+ Block* block_to_append_gap_to = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kReferenceBlock));
+
+ new TrackInsertBlockAfterCommand(parent()->GetTrackFromReference(ghost->Track()),
+ gap,
+ block_to_append_gap_to,
+ command);
+ }
+ } else {
+ // This was a Block that already existed
+ if (ghost->AdjustedLength() > 0) {
+ if (movement_mode == Timeline::kTrimIn) {
+ // We'll need to shift the media in point too
+ new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
+ } else {
+ new BlockResizeCommand(b, ghost->AdjustedLength(), command);
+ }
+ } else {
+ // Assumed the Block was a Gap and it was reduced to zero length, remove it here
+ new TrackRippleRemoveBlockCommand(parent()->GetTrackFromReference(ghost->Track()), b, command);
+
+ new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command);
+ }
+ }
+ }
+
+ Core::instance()->undo_stack()->pushIfHasChildren(command);
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/timelinewidget/tool/rolling.cpp b/app/widget/timelinewidget/tool/rolling.cpp
index 38babb4e8..f70ba9f7d 100644
--- a/app/widget/timelinewidget/tool/rolling.cpp
+++ b/app/widget/timelinewidget/tool/rolling.cpp
@@ -29,77 +29,47 @@ TimelineWidget::RollingTool::RollingTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
+ SetTrimOverwriteAllowed(true);
}
-void TimelineWidget::RollingTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
+void TimelineWidget::RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming)
{
- Q_UNUSED(event)
+ PointerTool::InitiateDrag(clicked_item, trim_mode, true);
+ // 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_) {
- Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
+ if (ghost->mode() == drag_movement_mode()) {
+ Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
- if (ghost->mode() == Timeline::kTrimIn) {
- if (b->previous() == nullptr) {
- // We'll need to insert a gap here, so we'll do a Place command instead
- GapBlock* gap = new GapBlock();
- gap->set_length_and_media_out(ghost->Length());
- new NodeAddCommand(static_cast(b->parent()),
- gap,
- command);
-
- new TrackReplaceBlockCommand(parent()->GetTrackFromReference(ghost->Track()), b, gap, command);
- }
-
- new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
-
- if (b->previous() == nullptr) {
- const TrackReference& track_ref = ghost->Track();
-
- new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()),
- track_ref.index(),
- b,
- ghost->GetAdjustedIn(),
- command);
- }
- } else if (ghost->mode() == Timeline::kTrimOut) {
- new BlockResizeCommand(b, ghost->AdjustedLength(), command);
+ BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->Track()),
+ b,
+ ghost->AdjustedLength(),
+ drag_movement_mode(),
+ command);
+ c->SetAllowNonGapTrimming(true);
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
-rational TimelineWidget::RollingTool::FrameValidateInternal(rational time_movement, const QVector &ghosts)
-{
- // Only validate trimming, and we don't care about "overwriting" since the rolling tool is designed to trim at collisions
- time_movement = ValidateInTrimming(time_movement, ghosts, false);
- time_movement = ValidateOutTrimming(time_movement, ghosts, false);
-
- return time_movement;
-}
-
-void TimelineWidget::RollingTool::InitiateGhosts(TimelineViewBlockItem *clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming)
-{
- Q_UNUSED(allow_gap_trimming)
-
- PointerTool::InitiateGhosts(clicked_item, trim_mode, true);
-
- // 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() != nullptr) {
- // 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() != nullptr) {
- AddGhostFromBlock(ghost_block->next(), ghost->Track(), Timeline::kTrimIn);
- }
- }
-}
-
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/timelinewidget/tool/slide.cpp b/app/widget/timelinewidget/tool/slide.cpp
index beb60a4cf..3d53e3078 100644
--- a/app/widget/timelinewidget/tool/slide.cpp
+++ b/app/widget/timelinewidget/tool/slide.cpp
@@ -30,65 +30,107 @@ TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) :
{
SetTrimmingAllowed(false);
SetTrackMovementAllowed(false);
+ SetTrimOverwriteAllowed(true);
}
-void TimelineWidget::SlideTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
-{
- Q_UNUSED(event)
+struct TrackBlockListPair {
+ TrackReference track;
+ QList blocks;
+};
- QUndoCommand* command = new QUndoCommand();
-
- // Find earliest point to ripple around
- foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
- Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
-
- if (ghost->mode() == Timeline::kTrimIn) {
- new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
- } else if (ghost->mode() == Timeline::kTrimOut) {
- new BlockResizeCommand(b, ghost->AdjustedLength(), command);
- } else if (ghost->mode() == Timeline::kMove && b->previous() == nullptr) {
- GapBlock* gap = new GapBlock();
- gap->set_length_and_media_out(ghost->InAdjustment());
- new NodeAddCommand(static_cast(b->parent()), gap, command);
- new TrackPrependBlockCommand(parent()->GetTrackFromReference(ghost->Track()), gap, command);
- }
- }
-
- Core::instance()->undo_stack()->pushIfHasChildren(command);
-}
-
-rational TimelineWidget::SlideTool::FrameValidateInternal(rational time_movement, const QVector &ghosts)
-{
- // Only validate trimming, and we don't care about "overwriting" since the rolling tool is designed to trim at collisions
- time_movement = ValidateInTrimming(time_movement, ghosts, false);
- time_movement = ValidateOutTrimming(time_movement, ghosts, false);
-
- return time_movement;
-}
-
-void TimelineWidget::SlideTool::InitiateGhosts(TimelineViewBlockItem *clicked_item,
- Timeline::MovementMode trim_mode,
- bool allow_gap_trimming)
+void TimelineWidget::SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
+ Timeline::MovementMode trim_mode,
+ bool allow_gap_trimming)
{
Q_UNUSED(allow_gap_trimming)
- PointerTool::InitiateGhosts(clicked_item, trim_mode, true);
+ PointerTool::InitiateDrag(clicked_item, trim_mode, true);
- // For each ghost, we make an equivalent Ghost on the next/previous block
+ // Sort blocks into tracks
+ QList blocks_per_track;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
- Block* ghost_block = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
+ Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock));
+ bool found = false;
- // Add trimming ghosts for each side of the Block
-
- if (ghost_block->previous() != nullptr) {
- // Add an extra Ghost for the previous block
- AddGhostFromBlock(ghost_block->previous(), ghost->Track(), Timeline::kTrimOut);
+ for (int i=0;iTrack()) {
+ blocks_per_track[i].blocks.append(b);
+ found = true;
+ break;
+ }
}
- if (ghost_block->next() != nullptr) {
- AddGhostFromBlock(ghost_block->next(), ghost->Track(), Timeline::kTrimIn);
+ 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));
+ }
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp
index a548993e4..0208743be 100644
--- a/app/widget/timelinewidget/tool/slip.cpp
+++ b/app/widget/timelinewidget/tool/slip.cpp
@@ -51,22 +51,18 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
ghost->SetMediaInAdjustment(time_movement);
}
- // Show tooltip
- // Generate tooltip (showing earliest in point of imported clip)
- int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase());
- QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
- parent()->GetToolTipTimebase(),
- Core::instance()->GetTimecodeDisplay(),
- true);
- // Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
- // of the cursor)
+ // 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)
QToolTip::hideText();
QToolTip::showText(QCursor::pos(),
- tooltip_text,
+ Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, parent()->GetToolTipTimebase()),
+ parent()->GetToolTipTimebase(),
+ Core::instance()->GetTimecodeDisplay(),
+ true),
parent());
}
-void TimelineWidget::SlipTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
+void TimelineWidget::SlipTool::FinishDrag(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp
index 957b2dca9..e12b41139 100644
--- a/app/widget/timelinewidget/tool/tool.cpp
+++ b/app/widget/timelinewidget/tool/tool.cpp
@@ -98,7 +98,7 @@ void AttemptSnap(const QList& proposed_pts,
}
}
-rational TimelineWidget::Tool::ValidateFrameMovement(rational movement, const QVector ghosts)
+rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVector ghosts)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
if (ghost->mode() != Timeline::kMove) {
diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp
index 3aee9318c..45ba69b20 100644
--- a/app/widget/timelinewidget/undo/undo.cpp
+++ b/app/widget/timelinewidget/undo/undo.cpp
@@ -21,8 +21,9 @@
#include "undo.h"
#include "core.h"
-#include "node/graph.h"
+#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
+#include "node/graph.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -342,6 +343,11 @@ TrackPlaceBlockCommand::TrackPlaceBlockCommand(TrackList *timeline, int track, B
insert_ = block;
}
+Project *TrackPlaceBlockCommand::GetRelevantProject() const
+{
+ return static_cast(static_cast(timeline_->parent())->parent())->project();
+}
+
void TrackPlaceBlockCommand::redo_internal()
{
added_track_count_ = 0;
@@ -425,8 +431,7 @@ Project *BlockSplitCommand::GetRelevantProject() const
void BlockSplitCommand::redo_internal()
{
- // FIXME: Reintroduce this optimization when block waveforms update automatically
- // track_->BlockInvalidateCache();
+ track_->BlockInvalidateCache();
static_cast(block_->parent())->AddNode(new_block_);
Node::CopyInputs(block_, new_block_);
@@ -444,12 +449,16 @@ void BlockSplitCommand::redo_internal()
NodeParam::ConnectEdge(new_block_->output(), transition);
}
- // track_->UnblockInvalidateCache();
+ if (block_->type() == Block::kClip) {
+ static_cast(new_block_)->set_waveform(static_cast(block_)->waveform().Cut(new_length_));
+ }
+
+ track_->UnblockInvalidateCache();
}
void BlockSplitCommand::undo_internal()
{
- // track_->BlockInvalidateCache();
+ track_->BlockInvalidateCache();
block_->set_length_and_media_out(old_length_);
track_->RippleRemoveBlock(new_block_);
@@ -461,7 +470,11 @@ void BlockSplitCommand::undo_internal()
NodeParam::ConnectEdge(block_->output(), transition);
}
- // track_->UnblockInvalidateCache();
+ if (block_->type() == Block::kClip) {
+ static_cast(block_)->waveform().Append(static_cast(new_block_)->waveform());
+ }
+
+ track_->UnblockInvalidateCache();
}
Block *BlockSplitCommand::new_block()
@@ -593,6 +606,7 @@ Project *BlockSplitPreservingLinksCommand::GetRelevantProject() const
return static_cast(blocks_.first()->parent())->project();
}
+/*
TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, QUndoCommand *parent) :
UndoCommand(parent),
track_list_(track_list),
@@ -686,6 +700,7 @@ void TrackCleanGapsCommand::undo_internal()
merged_gaps_.clear();
}
+*/
BlockSetSpeedCommand::BlockSetSpeedCommand(Block *block, const rational &new_speed, QUndoCommand *parent) :
UndoCommand(parent),
@@ -923,4 +938,413 @@ void BlockEnableDisableCommand::undo_internal()
block_->set_enabled(old_enabled_);
}
+BlockTrimCommand::BlockTrimCommand(TrackOutput* track, Block *block, rational new_length, Timeline::MovementMode mode, QUndoCommand *command) :
+ UndoCommand(command),
+ track_(track),
+ block_(block),
+ old_length_(block->length()),
+ new_length_(new_length),
+ mode_(mode),
+ adjacent_(nullptr),
+ we_created_adjacent_(false),
+ allow_nongap_trimming_(false)
+{
+}
+
+Project *BlockTrimCommand::GetRelevantProject() const
+{
+ return static_cast(block_->parent())->project();
+}
+
+void BlockTrimCommand::redo_internal()
+{
+ track_->BlockInvalidateCache();
+
+ // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
+ rational trim_diff = old_length_ - new_length_;
+
+ TimeRange invalidate_range;
+
+ if (mode_ == Timeline::kTrimIn) {
+ invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
+ block_->set_length_and_media_in(new_length_);
+ adjacent_ = block_->previous();
+ } else {
+ invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
+ block_->set_length_and_media_out(new_length_);
+ adjacent_ = block_->next();
+ }
+
+ if (trim_diff > rational()) {
+ // If trimming SHORTER, we'll need to create/modify a gap
+ if (adjacent_ && (adjacent_->type() == Block::kGap || allow_nongap_trimming_)) {
+
+ // A gap (or equivalent) exists, simply increase the size of it
+ if (mode_ == Timeline::kTrimIn) {
+ adjacent_->set_length_and_media_out(adjacent_->length() + trim_diff);
+ } else {
+ adjacent_->set_length_and_media_in(adjacent_->length() + trim_diff);
+ }
+
+ } else {
+
+ // Don't create a gap if the trim was at the end of the sequence (which would be indicated by
+ // the mode being "trim out" and "block_->next()" being null.
+ if (mode_ == Timeline::kTrimIn || block_->next()) {
+ // We must create a gap
+ we_created_adjacent_ = true;
+
+ adjacent_ = new GapBlock();
+ adjacent_->set_length_and_media_out(trim_diff);
+ static_cast(track_->parent())->AddNode(adjacent_);
+
+ if (mode_ == Timeline::kTrimIn) {
+ track_->InsertBlockBefore(adjacent_, block_);
+ } else {
+ track_->InsertBlockAfter(adjacent_, block_);
+ }
+ }
+
+ }
+
+ if (block_->type() == Block::kClip) {
+ if (mode_ == Timeline::kTrimIn) {
+ static_cast(block_)->waveform().TrimIn(trim_diff);
+ } else {
+ static_cast(block_)->waveform().TrimOut(trim_diff);
+ }
+ }
+ } else {
+ if (adjacent_) {
+ // If trimming LONGER, we'll need to trim the adjacent
+ // (assume if there's no adjacent, we're at the end of the timeline and do nothing)
+ if (mode_ == Timeline::kTrimIn) {
+ adjacent_->set_length_and_media_out(adjacent_->length() + trim_diff);
+ } else {
+ adjacent_->set_length_and_media_in(adjacent_->length() + trim_diff);
+ }
+ }
+
+ if (block_->type() == Block::kClip) {
+ if (mode_ == Timeline::kTrimIn) {
+ static_cast(block_)->waveform().PrependSilence(-trim_diff);
+ } else {
+ static_cast(block_)->waveform().AppendSilence(-trim_diff);
+ }
+ }
+ }
+
+ track_->UnblockInvalidateCache();
+
+ track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
+}
+
+void BlockTrimCommand::undo_internal()
+{
+ track_->BlockInvalidateCache();
+
+ // Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
+ rational trim_diff = old_length_ - new_length_;
+
+ if (trim_diff > rational()) {
+ // If trimmed SHORTER, we need to unadjust the gap
+ if (we_created_adjacent_) {
+ // If we created a gap, just remove it straight up
+ track_->RippleRemoveBlock(adjacent_);
+ TakeNodeFromParentGraph(adjacent_);
+ delete adjacent_;
+ adjacent_ = nullptr;
+ we_created_adjacent_ = false;
+ } else if (adjacent_) {
+ // If we adjusted an existing gap, unadjust here
+ adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
+ }
+
+ if (block_->type() == Block::kClip) {
+ if (mode_ == Timeline::kTrimIn) {
+ static_cast(block_)->waveform().PrependSilence(trim_diff);
+ } else {
+ static_cast(block_)->waveform().AppendSilence(trim_diff);
+ }
+ }
+ } else {
+ if (adjacent_) {
+ // If trimmed LONGER, we adjusted an existing block
+ // (assume if there's no adjacent, we're at the end of the timeline and do nothing)
+ if (mode_ == Timeline::kTrimIn) {
+ adjacent_->set_length_and_media_out(adjacent_->length() - trim_diff);
+ }
+ }
+
+ if (block_->type() == Block::kClip) {
+ if (mode_ == Timeline::kTrimIn) {
+ static_cast(block_)->waveform().TrimIn(-trim_diff);
+ } else {
+ static_cast(block_)->waveform().TrimOut(-trim_diff);
+ }
+ }
+ }
+
+ TimeRange invalidate_range;
+
+ if (mode_ == Timeline::kTrimIn) {
+ block_->set_length_and_media_in(old_length_);
+ invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
+ } else {
+ block_->set_length_and_media_out(old_length_);
+ invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
+ }
+
+ track_->UnblockInvalidateCache();
+
+ track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
+}
+
+TrackReplaceBlockWithGapCommand::TrackReplaceBlockWithGapCommand(TrackOutput *track, Block *block, QUndoCommand *command) :
+ UndoCommand(command),
+ track_(track),
+ block_(block),
+ we_created_gap_(false),
+ gap_(nullptr),
+ merged_gap_(nullptr)
+{
+}
+
+Project *TrackReplaceBlockWithGapCommand::GetRelevantProject() const
+{
+ return static_cast(block_->parent())->project();
+}
+
+void TrackReplaceBlockWithGapCommand::redo_internal()
+{
+ TimeRange invalidate_range;
+
+ track_->BlockInvalidateCache();
+
+ // If the block has no next, it's at the end of the track and there's no need to create a gap
+ if (block_->next()) {
+ invalidate_range = TimeRange(block_->in(), block_->out());
+
+ rational new_gap_length = block_->length();
+
+ bool previous_is_a_gap = (block_->previous() && block_->previous()->type() == Block::kGap);
+ bool next_is_a_gap = (block_->next() && block_->next()->type() == Block::kGap);
+
+ if (previous_is_a_gap) {
+ // Extend gap before this block
+ gap_ = static_cast(block_->previous());
+
+ // If the next is also a gap, we'll merge the two
+ if (next_is_a_gap) {
+ merged_gap_ = static_cast(block_->next());
+
+ new_gap_length += merged_gap_->length();
+ track_->RippleRemoveBlock(merged_gap_);
+ TakeNodeFromParentGraph(merged_gap_, &memory_manager_);
+ }
+ } else if (next_is_a_gap) {
+ // Extend gap after this block
+ gap_ = static_cast(block_->next());
+ }
+
+ if (gap_) {
+ // Extend an existing gap
+ new_gap_length += gap_->length();
+ gap_->set_length_and_media_out(new_gap_length);
+ track_->RippleRemoveBlock(block_);
+ } else {
+ // No gap exists, create one
+ gap_ = new GapBlock();
+ gap_->set_length_and_media_out(new_gap_length);
+ static_cast(track_->parent())->AddNode(gap_);
+ track_->ReplaceBlock(block_, gap_);
+ we_created_gap_ = true;
+ }
+
+ } else {
+ rational earliest_change = block_->in();
+
+ // Handle the gap being at the end where no gap is necessary
+ track_->RippleRemoveBlock(block_);
+
+ // If there were also gaps leading up to this block, clean them up here
+ if (!track_->Blocks().isEmpty() && track_->Blocks().last()->type() == Block::kGap) {
+ merged_gap_ = static_cast(track_->Blocks().last());
+ earliest_change = merged_gap_->in();
+ track_->RippleRemoveBlock(merged_gap_);
+ TakeNodeFromParentGraph(merged_gap_, &memory_manager_);
+ }
+
+ invalidate_range = TimeRange(earliest_change, RATIONAL_MAX);
+ }
+
+ track_->UnblockInvalidateCache();
+
+ track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
+}
+
+void TrackReplaceBlockWithGapCommand::undo_internal()
+{
+ TimeRange invalidate_range;
+
+ track_->BlockInvalidateCache();
+
+ if (gap_) {
+
+ if (we_created_gap_) {
+ // We made this gap, simply swap our gap back
+ track_->ReplaceBlock(gap_, block_);
+ TakeNodeFromParentGraph(gap_);
+ delete gap_;
+ gap_ = nullptr;
+ } else {
+ // We must have extended an existing gap
+ rational original_gap_length = gap_->length() - block_->length();
+
+ // If we merged two gaps together, restore it now
+ if (merged_gap_) {
+ original_gap_length -= merged_gap_->length();
+ static_cast(track_->parent())->AddNode(merged_gap_);
+ track_->InsertBlockAfter(merged_gap_, gap_);
+ }
+
+ // Restore original block
+ track_->InsertBlockAfter(block_, gap_);
+
+ // Restore gap's original length
+ gap_->set_length_and_media_out(original_gap_length);
+ }
+
+ invalidate_range = TimeRange(block_->in(), block_->out());
+
+ } else {
+
+ // If there's no `gap_`, we must have removed the block at the end
+ invalidate_range = TimeRange(track_->length(), RATIONAL_MAX);
+
+ if (merged_gap_) {
+ static_cast(track_->parent())->AddNode(merged_gap_);
+ track_->AppendBlock(merged_gap_);
+ }
+
+ track_->AppendBlock(block_);
+
+ }
+
+ merged_gap_ = nullptr;
+
+ track_->UnblockInvalidateCache();
+
+ track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
+}
+
+TrackSlideCommand::TrackSlideCommand(const QVector &blocks, QUndoCommand *parent) :
+ UndoCommand(parent),
+ blocks_(blocks)
+{
+}
+
+Project *TrackSlideCommand::GetRelevantProject() const
+{
+ return static_cast(blocks_.first().track->parent())->project();
+}
+
+void TrackSlideCommand::redo_internal()
+{
+ slide_internal(false);
+}
+
+void TrackSlideCommand::undo_internal()
+{
+ slide_internal(true);
+}
+
+void TrackSlideCommand::slide_internal(bool undo)
+{
+ QMap invalidate_ranges;
+
+ // Make sure all movement blocks' old positions are invalidated
+ foreach (const BlockSlideInfo& info, blocks_) {
+ if (info.mode == Timeline::kMove) {
+ invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
+ info.block->out()));
+ }
+ }
+
+ // Perform trims
+ foreach (const BlockSlideInfo& info, blocks_) {
+ info.track->BlockInvalidateCache();
+
+ if (info.mode == Timeline::kTrimIn || info.mode == Timeline::kTrimOut) {
+ rational new_len = undo ? info.old_time : info.new_time;
+
+ if (info.block->type() == Block::kClip) {
+ AudioVisualWaveform& waveform = static_cast(info.block)->waveform();
+
+ if (new_len < info.block->length()) {
+ if (info.mode == Timeline::kTrimIn) {
+ waveform.TrimIn(info.block->length() - new_len);
+ } else {
+ waveform.TrimOut(info.block->length() - new_len);
+ }
+ } else {
+ if (info.mode == Timeline::kTrimIn) {
+ waveform.PrependSilence(new_len - info.block->length());
+ } else {
+ waveform.AppendSilence(new_len - info.block->length());
+ }
+ }
+ }
+
+ if (info.mode == Timeline::kTrimIn) {
+ info.block->set_length_and_media_in(new_len);
+ } else {
+ info.block->set_length_and_media_out(new_len);
+ }
+ } else if (!undo && info.mode == Timeline::kMove && !info.block->previous()) {
+ // If this is a moving block and there was nothing before it to offset its time correctly,
+ // insert a gap here
+ GapBlock* gap = new GapBlock();
+ gap->set_length_and_media_out(info.new_time);
+ static_cast(info.block->parent())->AddNode(gap);
+ info.track->PrependBlock(gap);
+ added_gaps_.append(gap);
+ }
+
+ info.track->UnblockInvalidateCache();
+ }
+
+ if (undo) {
+ // If undoing, remove added gaps
+ foreach (GapBlock* gap, added_gaps_) {
+ TrackOutput* track = TrackOutput::TrackFromBlock(gap);
+
+ track->BlockInvalidateCache();
+
+ track->RippleRemoveBlock(gap);
+ TakeNodeFromParentGraph(gap);
+ delete gap;
+
+ track->UnblockInvalidateCache();
+ }
+
+ added_gaps_.clear();
+ }
+
+ // Make sure all movement blocks' new positions are invalidated
+ foreach (const BlockSlideInfo& info, blocks_) {
+ if (info.mode == Timeline::kMove) {
+ invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
+ info.block->out()));
+ }
+ }
+
+ QMap::const_iterator i;
+ for (i=invalidate_ranges.constBegin(); i!=invalidate_ranges.constEnd(); i++) {
+ foreach (const TimeRange& r, i.value()) {
+ i.key()->InvalidateCache(r, i.key()->block_input(), i.key()->block_input());
+ }
+ }
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h
index d763d007c..8d6957ca5 100644
--- a/app/widget/timelinewidget/undo/undo.h
+++ b/app/widget/timelinewidget/undo/undo.h
@@ -64,6 +64,35 @@ private:
rational new_length_;
};
+class BlockTrimCommand : public UndoCommand {
+public:
+ BlockTrimCommand(TrackOutput *track, Block* block, rational new_length, Timeline::MovementMode mode, QUndoCommand* command = nullptr);
+
+ virtual Project* GetRelevantProject() const override;
+
+ void SetAllowNonGapTrimming(bool e)
+ {
+ allow_nongap_trimming_ = e;
+ }
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ TrackOutput* track_;
+ Block* block_;
+ rational old_length_;
+ rational new_length_;
+ Timeline::MovementMode mode_;
+
+ Block* adjacent_;
+ bool we_created_adjacent_;
+
+ bool allow_nongap_trimming_;
+
+};
+
class BlockSetMediaInCommand : public UndoCommand {
public:
BlockSetMediaInCommand(Block* block, rational new_media_in, QUndoCommand* parent = nullptr);
@@ -168,6 +197,8 @@ protected:
virtual void undo_internal() override;
protected:
+ Project* project_;
+
TrackOutput* track_;
rational in_;
rational out_;
@@ -203,6 +234,8 @@ class TrackPlaceBlockCommand : public TrackRippleRemoveAreaCommand {
public:
TrackPlaceBlockCommand(TrackList *timeline, int track, Block* block, rational in, QUndoCommand* parent = nullptr);
+ virtual Project* GetRelevantProject() const override;
+
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
@@ -287,6 +320,29 @@ private:
Block* replace_;
};
+class TrackReplaceBlockWithGapCommand : public UndoCommand {
+public:
+ TrackReplaceBlockWithGapCommand(TrackOutput* track, Block* block, QUndoCommand* command = nullptr);
+
+ virtual Project* GetRelevantProject() const override;
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ TrackOutput* track_;
+ Block* block_;
+
+ bool we_created_gap_;
+ GapBlock* gap_;
+ GapBlock* merged_gap_;
+
+ QObject memory_manager_;
+
+};
+
+/*
class TrackCleanGapsCommand : public UndoCommand {
public:
TrackCleanGapsCommand(TrackList* track_list, int index, QUndoCommand* parent = nullptr);
@@ -315,6 +371,7 @@ private:
QList removed_end_gaps_;
};
+*/
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
public:
@@ -444,6 +501,32 @@ private:
};
+class TrackSlideCommand : public UndoCommand {
+public:
+ struct BlockSlideInfo {
+ TrackOutput* track;
+ Block* block;
+ Timeline::MovementMode mode;
+ rational new_time;
+ rational old_time;
+ };
+
+ TrackSlideCommand(const QVector& blocks, QUndoCommand* parent = nullptr);
+
+ virtual Project* GetRelevantProject() const override;
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ void slide_internal(bool undo);
+
+ QVector blocks_;
+ QList added_gaps_;
+
+};
+
OLIVE_NAMESPACE_EXIT
#endif // TIMELINEUNDOABLE_H
diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp
index 1b63d0d3e..48df43b20 100644
--- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp
+++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp
@@ -96,29 +96,11 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
}
// Draw waveform if one is available
- QString wave_fn = QDir(QDir(Config::Current()["DiskCachePath"].toString()).filePath("waveform")).filePath(QString::number(reinterpret_cast(block_)));
- QFile wave_file(wave_fn);
- if (wave_file.open(QFile::ReadOnly)){
- painter->setPen(QColor(64, 64, 64));
-
- QByteArray w = wave_file.readAll();
-
- wave_file.close();
-
- // Read metadata
- SampleSummer::Info info;
- memcpy(&info, w.data(), sizeof(SampleSummer::Info));
-
- // Prevent divide by zero
- if (info.channels) {
- AudioWaveformView::DrawWaveform(painter,
- rect().toRect(),
- this->GetScale(),
- reinterpret_cast(w.constData() + sizeof(SampleSummer::Info)),
- (w.size() - sizeof(SampleSummer::Info)) / sizeof(SampleSummer::Sum),
- info.channels);
- }
- }
+ painter->setPen(QColor(64, 64, 64));
+ AudioVisualWaveform::DrawWaveform(painter,
+ rect().toRect(),
+ this->GetScale(),
+ static_cast(block_)->waveform());
painter->setPen(Qt::white);
painter->drawLine(rect().topLeft(), QPointF(rect().right(), rect().top()));
diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.h b/app/widget/timelinewidget/view/timelineviewghostitem.h
index 06f8018b7..373562900 100644
--- a/app/widget/timelinewidget/view/timelineviewghostitem.h
+++ b/app/widget/timelinewidget/view/timelineviewghostitem.h
@@ -23,8 +23,8 @@
#include
-#include "common/timelinecommon.h"
#include "project/item/footage/footage.h"
+#include "timeline/timelinecommon.h"
#include "timelineviewblockitem.h"
#include "timelineviewrect.h"
diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp
index 3e139189e..21dbb9f70 100644
--- a/app/widget/viewer/audiowaveformview.cpp
+++ b/app/widget/viewer/audiowaveformview.cpp
@@ -58,67 +58,6 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
ForceUpdate();
}
-void AudioWaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels)
-{
- int sample_index, next_sample_index = 0;
-
- QVector summary;
- int summary_index = -1;
-
- int channel_height = rect.height() / channels;
- int channel_half_height = channel_height / 2;
-
- const QRect& viewport = painter->viewport();
- QPoint top_left = painter->transform().map(viewport.topLeft());
-
- int start = qMax(rect.x(), -top_left.x());
- int end = qMin(rect.right(), -top_left.x() + viewport.width());
-
- QVector lines;
-
- for (int i=start;i(SampleSummer::kSumSampleRate) * static_cast(i - rect.x() + 1) / scale) * channels);
-
- if (summary_index != sample_index) {
- summary = SampleSummer::ReSumSamples(&samples[sample_index],
- qMax(channels, next_sample_index - sample_index),
- channels);
- summary_index = sample_index;
- }
-
- int line_x = i;
-
- for (int j=0;jdrawLines(lines);
-}
-
void AudioWaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
@@ -147,15 +86,11 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
// FIXME: Hardcoded color
wave_painter.setPen(QColor(64, 255, 160));
- int channel_height = height() / params.channel_count();
- int channel_half_height = channel_height / 2;
-
int drew = 0;
fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
for (int x=0; x samples = SampleSummer::SumSamples(reinterpret_cast(read_buffer.constData()),
- samples_len,
- params.channel_count());
+ QVector samples = AudioVisualWaveform::SumSamples(reinterpret_cast(read_buffer.constData()),
+ samples_len,
+ params.channel_count());
for (int i=0;i(channel_half_height)),
- x,
- channel_mid + qRound(samples.at(i).max * static_cast(channel_half_height)));
- }
+ AudioVisualWaveform::DrawSample(&wave_painter, samples, x, 0, height());
drew++;
}
diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h
index e43999817..13540d394 100644
--- a/app/widget/viewer/audiowaveformview.h
+++ b/app/widget/viewer/audiowaveformview.h
@@ -23,7 +23,7 @@
#include
-#include "audio/sumsamples.h"
+#include "audio/audiovisualwaveform.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
#include "widget/timeruler/seekablewidget.h"
@@ -40,8 +40,6 @@ public:
void SetViewer(AudioPlaybackCache *playback);
- static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const SampleSummer::Sum *samples, int nb_samples, int channels);
-
protected:
virtual void paintEvent(QPaintEvent* event) override;