Merge branch 'master' into otio

This commit is contained in:
itsmattkc
2020-10-22 20:26:16 +11:00
149 changed files with 4350 additions and 2079 deletions
+23 -22
View File
@@ -12,17 +12,16 @@ on:
- 'README.md'
env:
DOWNLOAD_TOOL: curl -fLOSs --retry 3
DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60
UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60
jobs:
linux:
strategy:
fail-fast: false
matrix:
build: [1, 2]
include:
- build: 1
build-type: RelWithDebInfo
- build-type: RelWithDebInfo
cc-compiler: gcc
cxx-compiler: g++
compiler-name: GCC 9.3.1
@@ -30,8 +29,7 @@ jobs:
os-name: Linux (CentOS 7)
vfx-cy: 2021
ci-common-version: 2
- build: 2
build-type: RelWithDebInfo
- build-type: RelWithDebInfo
cc-compiler: clang
cxx-compiler: clang++
compiler-name: Clang 10.0.0
@@ -49,6 +47,8 @@ jobs:
runs-on: ubuntu-latest
container:
image: olivevideoeditor/ci-olive:${{ matrix.vfx-cy }}.${{ matrix.ci-common-version }}
if: >-
!contains(github.event.head_commit.message, '[skip ci]')
steps:
- name: Checkout Source Code
@@ -105,7 +105,7 @@ jobs:
PKGNAME: ${{ steps.package.outputs.pkgname }}
if: github.event_name == 'push' && matrix.cc-compiler == 'gcc'
run: |
curl -X POST \
$UPLOAD_TOOL \
-F file="@$PKGNAME" \
-F key="$GH_AUTH_KEY" \
-F hash="$GITHUB_SHA" \
@@ -116,10 +116,8 @@ jobs:
windows:
strategy:
matrix:
build: [1]
include:
- build: 1
build-type: RelWithDebInfo
- build-type: RelWithDebInfo
compiler-name: MSVC 16.x
os-name: Windows
os-arch: x86_64
@@ -133,6 +131,8 @@ jobs:
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ${{ matrix.os }}
if: >-
!contains(github.event.head_commit.message, '[skip ci]')
steps:
- name: Checkout Source Code
@@ -144,17 +144,17 @@ jobs:
PLATFORM: ${{ matrix.os-name }}
ARCH: ${{ matrix.os-arch }}
run: |
echo "::set-env name=PKGNAME::$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})"
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
- name: Create Build Folder
run: |
cmake -E make_directory ${{ runner.workspace }}/build
- name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.3.0
uses: ilammy/msvc-dev-cmd@v1
- name: Acquire Qt
uses: jurplel/install-qt-action@v2.8.0
uses: jurplel/install-qt-action@v2
with:
version: 5.15.1
@@ -254,7 +254,7 @@ jobs:
GH_AUTH_KEY: ${{ secrets.GH_AUTH_KEY }}
if: github.event_name == 'push'
run: |
curl -X POST \
$UPLOAD_TOOL \
-F file=@$PKGNAME.exe \
-F key="$GH_AUTH_KEY" \
-F hash="$GITHUB_SHA" \
@@ -262,7 +262,7 @@ jobs:
-F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" \
https://ci.olivevideoeditor.org/push.php
curl -X POST \
$UPLOAD_TOOL \
-F file=@$PKGNAME.zip \
-F key="$GH_AUTH_KEY" \
-F hash="$GITHUB_SHA" \
@@ -276,7 +276,8 @@ jobs:
env:
GH_AUTH_KEY: ${{ secrets.GH_AUTH_KEY }}
run: |
curl -fLSs --retry 3 https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true > dump_syms.exe
curl -fLSs --retry 2 --retry-delay 60 \
https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true > dump_syms.exe
./dump_syms app/olive-editor.pdb > olive-editor.sym
curl -X POST \
-F symfile=@olive-editor.sym \
@@ -286,10 +287,8 @@ jobs:
macos:
strategy:
matrix:
build: [1]
include:
- build: 1
build-type: RelWithDebInfo
- build-type: RelWithDebInfo
compiler-name: Clang LLVM
os-name: macOS
os-arch: x86_64
@@ -303,6 +302,8 @@ jobs:
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ${{ matrix.os }}
if: >-
!contains(github.event.head_commit.message, '[skip ci]')
steps:
- name: Checkout Source Code
@@ -314,14 +315,14 @@ jobs:
PLATFORM: ${{ matrix.os-name }}
ARCH: ${{ matrix.os-arch }}
run: |
echo "::set-env name=PKGNAME::$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})"
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
- name: Create Build Folder
run: |
cmake -E make_directory ${{ runner.workspace }}/build
- name: Acquire Qt
uses: jurplel/install-qt-action@v2.8.0
uses: jurplel/install-qt-action@v2
with:
version: 5.15.1
@@ -407,7 +408,7 @@ jobs:
GH_AUTH_KEY: ${{ secrets.GH_AUTH_KEY }}
if: github.event_name == 'push'
run: |
curl -X POST \
$UPLOAD_TOOL \
-F file=@$PKGNAME.zip \
-F key="$GH_AUTH_KEY" \
-F hash="$GITHUB_SHA" \
+5 -1
View File
@@ -185,6 +185,10 @@ if (OpenTimelineIO_FOUND)
)
endif()
if(UNIX AND NOT APPLE)
install(TARGETS ${OLIVE_TARGET} RUNTIME DESTINATION bin)
endif()
# Enable Crashpad if found
if (GoogleCrashpad_FOUND)
set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_CRASHPAD)
@@ -242,7 +246,7 @@ if (GoogleCrashpad_FOUND)
set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}")
if(UNIX AND NOT APPLE)
install(TARGETS ${OLIVE_TARGET} ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
install(TARGETS ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
install(PROGRAMS ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} DESTINATION bin)
install(PROGRAMS ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} DESTINATION bin)
endif()
+19 -11
View File
@@ -77,26 +77,33 @@ bool AudioManager::IsRefreshingInputs()
void AudioManager::PushToOutput(const QByteArray &samples)
{
output_manager_.Push(samples);
output_manager_->Push(samples);
emit OutputPushed(samples);
}
void AudioManager::StartOutput(const QString &filename, qint64 offset, int playback_speed)
void AudioManager::StartOutput(AudioPlaybackCache *cache, qint64 offset, int playback_speed)
{
QMetaObject::invokeMethod(&output_manager_,
// Create device
QIODevice* device = cache->CreatePlaybackDevice();
// Move to output manager's thread
device->moveToThread(&output_thread_);
// Queue to output manger in other thread
QMetaObject::invokeMethod(output_manager_,
"PullFromDevice",
Qt::QueuedConnection,
Q_ARG(const QString&, filename),
Q_ARG(QIODevice*, device),
Q_ARG(qint64, offset),
Q_ARG(int, playback_speed));
emit OutputDeviceStarted(filename, offset, playback_speed);
emit OutputDeviceStarted(cache, offset, playback_speed);
}
void AudioManager::StopOutput()
{
QMetaObject::invokeMethod(&output_manager_,
QMetaObject::invokeMethod(output_manager_,
"ResetToPushMode",
Qt::QueuedConnection);
@@ -149,7 +156,7 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
}
if (info.isFormatSupported(format)) {
QMetaObject::invokeMethod(&output_manager_,
QMetaObject::invokeMethod(output_manager_,
"SetOutputDevice",
Qt::QueuedConnection,
Q_ARG(const QAudioDeviceInfo&, info),
@@ -166,7 +173,7 @@ void AudioManager::SetOutputParams(const AudioParams &params)
if (output_params_ != params) {
output_params_ = params;
QMetaObject::invokeMethod(&output_manager_,
QMetaObject::invokeMethod(output_manager_,
"SetParameters",
Qt::QueuedConnection,
OLIVE_NS_ARG(AudioParams, params));
@@ -222,14 +229,15 @@ AudioManager::AudioManager() :
RefreshDevices();
output_thread_.start(QThread::TimeCriticalPriority);
output_manager_.moveToThread(&output_thread_);
output_manager_ = new AudioOutputManager();
output_manager_->moveToThread(&output_thread_);
connect(&output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified);
connect(output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified);
}
AudioManager::~AudioManager()
{
QMetaObject::invokeMethod(&output_manager_, "Close", Qt::QueuedConnection);
QMetaObject::invokeMethod(output_manager_, "deleteLater", Qt::BlockingQueuedConnection);
output_thread_.quit();
output_thread_.wait();
}
+5 -6
View File
@@ -30,6 +30,7 @@
#include "common/define.h"
#include "outputmanager.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
OLIVE_NAMESPACE_ENTER
@@ -57,11 +58,9 @@ public:
void PushToOutput(const QByteArray& samples);
/**
* @brief Start playing audio from QIODevice
*
* This takes ownership of the QIODevice and will delete it when StopOutput() is called
* @brief Start playing audio from AudioPlaybackCache
*/
void StartOutput(const QString& filename, qint64 offset, int playback_speed);
void StartOutput(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
/**
* @brief Stop audio output immediately
@@ -86,7 +85,7 @@ signals:
void OutputNotified();
void OutputDeviceStarted(const QString& filename, qint64 offset, int playback_speed);
void OutputDeviceStarted(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
void AudioParamsChanged(const AudioParams& params);
@@ -108,7 +107,7 @@ private:
static AudioManager* instance_;
QThread output_thread_;
AudioOutputManager output_manager_;
AudioOutputManager* output_manager_;
bool output_is_set_;
QAudioDeviceInfo output_device_info_;
+21 -18
View File
@@ -24,11 +24,10 @@
OLIVE_NAMESPACE_ENTER
AudioOutputDeviceProxy::~AudioOutputDeviceProxy()
AudioOutputDeviceProxy::AudioOutputDeviceProxy(QObject *parent) :
QIODevice(parent),
device_(nullptr)
{
if (file_.isOpen()) {
file_.close();
}
}
void AudioOutputDeviceProxy::SetParameters(const AudioParams &params)
@@ -36,20 +35,23 @@ void AudioOutputDeviceProxy::SetParameters(const AudioParams &params)
params_ = params;
}
void AudioOutputDeviceProxy::SetDevice(const QString &filename, qint64 offset, int playback_speed)
void AudioOutputDeviceProxy::SetDevice(QIODevice* device, qint64 offset, int playback_speed)
{
if (file_.isOpen()) {
file_.close();
if (device_) {
delete device_;
}
file_.setFileName(filename);
device_ = device;
device_->setParent(this);
if (!file_.open(QFile::ReadOnly)) {
qCritical() << "Failed to open" << filename << "for audio playback";
if (!device_->open(QFile::ReadOnly)) {
qCritical() << "Failed to open IO device for audio playback";
delete device_;
device_ = nullptr;
return;
}
file_.seek(offset);
device_->seek(offset);
playback_speed_ = playback_speed;
@@ -62,7 +64,8 @@ void AudioOutputDeviceProxy::close()
{
QIODevice::close();
file_.close();
delete device_;
device_ = nullptr;
if (tempo_processor_.IsOpen()) {
tempo_processor_.Close();
@@ -71,7 +74,7 @@ void AudioOutputDeviceProxy::close()
qint64 AudioOutputDeviceProxy::readData(char *data, qint64 maxlen)
{
if (!file_.isOpen()) {
if (!device_) {
return 0;
}
@@ -111,21 +114,21 @@ qint64 AudioOutputDeviceProxy::ReverseAwareRead(char *data, qint64 maxlen)
if (playback_speed_ < 0) {
// If we're reversing, we'll seek back by maxlen bytes before we read
new_pos = file_.pos() - maxlen;
new_pos = device_->pos() - maxlen;
if (new_pos < 0) {
maxlen = file_.pos();
maxlen = device_->pos();
new_pos = 0;
}
file_.seek(new_pos);
device_->seek(new_pos);
}
qint64 read_count = file_.read(data, maxlen);
qint64 read_count = device_->read(data, maxlen);
if (playback_speed_ < 0) {
file_.seek(new_pos);
device_->seek(new_pos);
// Reverse the samples here
AudioManager::ReverseBuffer(data, static_cast<int>(read_count), params_.samples_to_bytes(1));
+3 -5
View File
@@ -35,13 +35,11 @@ class AudioOutputDeviceProxy : public QIODevice
{
Q_OBJECT
public:
AudioOutputDeviceProxy() = default;
virtual ~AudioOutputDeviceProxy() override;
AudioOutputDeviceProxy(QObject* parent = nullptr);
void SetParameters(const AudioParams& params);
void SetDevice(const QString &filename, qint64 offset, int playback_speed);
void SetDevice(QIODevice *device, qint64 offset, int playback_speed);
virtual void close() override;
@@ -53,7 +51,7 @@ protected:
private:
qint64 ReverseAwareRead(char* data, qint64 maxlen);
QFile file_;
QIODevice* device_;
TempoProcessor tempo_processor_;
+5 -4
View File
@@ -30,7 +30,8 @@ OLIVE_NAMESPACE_ENTER
AudioOutputManager::AudioOutputManager(QObject *parent) :
QObject(parent),
output_(nullptr),
push_device_(nullptr)
push_device_(nullptr),
device_proxy_(this)
{
}
@@ -82,12 +83,12 @@ void AudioOutputManager::Close()
device_proxy_.close();
}
output_->deleteLater();
delete output_;
output_ = nullptr;
}
}
void AudioOutputManager::PullFromDevice(const QString &filename, qint64 offset, int playback_speed)
void AudioOutputManager::PullFromDevice(QIODevice *device, qint64 offset, int playback_speed)
{
if (!output_) {
return;
@@ -99,7 +100,7 @@ void AudioOutputManager::PullFromDevice(const QString &filename, qint64 offset,
push_samples_.clear();
// Pull from the device
device_proxy_.SetDevice(filename, offset, playback_speed);
device_proxy_.SetDevice(device, offset, playback_speed);
device_proxy_.open(QIODevice::ReadOnly);
output_->start(&device_proxy_);
}
+1 -1
View File
@@ -53,7 +53,7 @@ public slots:
* This will clear any pushed samples or QIODevices currently being read and will start reading from this next time
* the audio output requests data.
*/
void PullFromDevice(const QString &filename, qint64 offset, int playback_speed);
void PullFromDevice(QIODevice* device, qint64 offset, int playback_speed);
// Queued
void ResetToPushMode();
+8
View File
@@ -20,6 +20,8 @@
#include "encoder.h"
#include <QFile>
#include "ffmpeg/ffmpegencoder.h"
OLIVE_NAMESPACE_ENTER
@@ -34,6 +36,12 @@ const EncodingParams &Encoder::params() const
return params_;
}
void Encoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename)
{
QFile f(pcm_filename);
WriteAudio(pcm_info, &f);
}
EncodingParams::EncodingParams() :
video_enabled_(false),
video_bit_rate_(0),
+3 -1
View File
@@ -116,7 +116,9 @@ public:
virtual bool WriteFrame(OLIVE_NAMESPACE::FramePtr frame, OLIVE_NAMESPACE::rational time) = 0;
virtual void WriteAudio(OLIVE_NAMESPACE::AudioParams pcm_info,
const QString& pcm_filename) = 0;
QIODevice *file) = 0;
void WriteAudio(OLIVE_NAMESPACE::AudioParams pcm_info,
const QString& pcm_filename);
virtual void Close() = 0;
+41 -12
View File
@@ -107,9 +107,11 @@ bool FFmpegDecoder::Open()
FFmpegFramePoolValue& frame_pool = frame_pool_map_[key];
if (!frame_pool.pool) {
// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make
// this a dynamic value somehow or a configurable value?
frame_pool.pool = new FFmpegFramePool(32);
// Frames are allocated as threads * threads, to scale from each thread sharing one set
// to all of them working individually
int thread_count = QThread::idealThreadCount();
int max_memory_frame_count = thread_count * thread_count;
frame_pool.pool = new FFmpegFramePool(max_memory_frame_count);
}
frame_pool.handles++;
@@ -574,6 +576,25 @@ ItemPtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancelle
audio_stream->set_channels(avstream->codecpar->channels);
audio_stream->set_sample_rate(avstream->codecpar->sample_rate);
if (avstream->duration == AV_NOPTS_VALUE) {
// Loop through stream until we get the whole duration
FFmpegDecoderInstance instance(filename, i);
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
int64_t new_dur;
do {
new_dur = frame->pts;
} while (instance.GetFrame(pkt, frame) >= 0);
avstream->duration = new_dur;
av_frame_free(&frame);
av_packet_free(&pkt);
}
str = audio_stream;
} else {
@@ -1087,6 +1108,9 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
break;
}
// Cut down to thread count - 1 before we acquire a new frame
TruncateCacheRangeToFrames(QThread::idealThreadCount() -1);
FFmpegFramePool::ElementPtr cached = frame_pool_->Get();
if (!cached) {
@@ -1127,14 +1151,6 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
previous = cached_frames_.last();
}
// Clear early frames
int removed = TruncateCacheRangeTo(second_ts_);
if (removed == 0 && MemoryPoolLimitReached()) {
// Relinquish a frame if we have to conserve memory
RemoveFirstFrame();
}
// Append this frame and signal to other threads that a new frame has arrived
cached_frames_.append(cached);
@@ -1322,7 +1338,7 @@ void FFmpegDecoderInstance::RemoveFramesBefore(const qint64 &t)
}
}
int FFmpegDecoderInstance::TruncateCacheRangeTo(const qint64 &t)
int FFmpegDecoderInstance::TruncateCacheRangeToTime(const qint64 &t)
{
int counter = 0;
@@ -1335,6 +1351,19 @@ int FFmpegDecoderInstance::TruncateCacheRangeTo(const qint64 &t)
return counter;
}
int FFmpegDecoderInstance::TruncateCacheRangeToFrames(int nb_frames)
{
int counter = 0;
// We keep one frame in memory as an identifier for what pts the decoder is up to
while (cached_frames_.size() > nb_frames) {
RemoveFirstFrame();
counter++;
}
return counter;
}
void FFmpegDecoderInstance::RemoveFirstFrame()
{
cached_frames_.removeFirst();
+2 -1
View File
@@ -62,7 +62,8 @@ public:
FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const;
void RemoveFramesBefore(const qint64& t);
int TruncateCacheRangeTo(const qint64& t);
int TruncateCacheRangeToTime(const qint64& t);
int TruncateCacheRangeToFrames(int nb_frames);
void RemoveFirstFrame();
AVFormatContext* fmt_ctx() const
+7 -6
View File
@@ -185,10 +185,9 @@ fail:
return success;
}
void FFmpegEncoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename)
void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file)
{
QFile pcm(pcm_filename);
if (pcm.open(QFile::ReadOnly)) {
if (file->open(QFile::ReadOnly)) {
// Divide PCM stream into AVFrames
// See if the codec defines a number of samples per frame
@@ -239,7 +238,7 @@ void FFmpegEncoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename
int max_read = pcm_info.samples_to_bytes(samples_needed);
// Read bytes from PCM
QByteArray input_data = pcm.read(max_read);
QByteArray input_data = file->read(max_read);
// Use swresample to convert the data into the correct format
const char* input_data_array = input_data.constData();
@@ -273,7 +272,7 @@ void FFmpegEncoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename
}
// Break if we've reached the end point
if (pcm.atEnd()) {
if (file->atEnd()) {
break;
}
}
@@ -282,7 +281,9 @@ void FFmpegEncoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename
swr_free(&swr_ctx);
pcm.close();
file->close();
} else {
qWarning() << "Failed to open audio IO device for encoding";
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ public:
virtual bool WriteFrame(OLIVE_NAMESPACE::FramePtr frame, OLIVE_NAMESPACE::rational time) override;
virtual void WriteAudio(OLIVE_NAMESPACE::AudioParams pcm_info,
const QString& pcm_filename) override;
QIODevice *file) override;
virtual void Close() override;
+1 -1
View File
@@ -86,7 +86,7 @@ bool InitializeCrashpad()
bool status = false;
if (QFileInfo::exists(handler_abs_path)) {
base::FilePath handler(QSTRING_TO_BASE_STRING(handler_fn));
base::FilePath handler(QSTRING_TO_BASE_STRING(handler_abs_path));
base::FilePath reports_dir = GenerateReportPathForCrashpad();
+14 -16
View File
@@ -160,20 +160,25 @@ void TimeRange::normalize()
length_ = out_ - in_;
}
void TimeRangeList::InsertTimeRange(const TimeRange &range)
void TimeRangeList::InsertTimeRange(TimeRange range_to_add)
{
// See if list contains this range
if (ContainsTimeRange(range_to_add)) {
return;
}
// Does not contain range, so we'll almost certainly be adding it in some way
for (int i=0;i<size();i++) {
const TimeRange& compare = at(i);
if (compare == range) {
return;
} else if (range.OverlapsWith(compare)) {
replace(i, TimeRange::Combine(range, compare));
return;
if (compare.OverlapsWith(range_to_add)) {
range_to_add = TimeRange::Combine(range_to_add, compare);
removeAt(i);
i--;
}
}
append(range);
append(range_to_add);
}
void TimeRangeList::RemoveTimeRange(const TimeRange &remove)
@@ -190,15 +195,8 @@ void TimeRangeList::RemoveTimeRange(const TimeRange &remove)
sz--;
} else if (compare.Contains(remove, false, false)) {
// The remove range is within this element, only choice is to split the element into two
TimeRange before(compare.in(), remove.in());
TimeRange after(remove.out(), compare.out());
this->removeAt(i);
i--;
sz--;
InsertTimeRange(before);
InsertTimeRange(after);
this->append(TimeRange(remove.out(), compare.out()));
compare.set_out(remove.in());
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
// This element's out point overlaps the range's in, we'll trim it
compare.set_out(remove.in());
+1 -1
View File
@@ -74,7 +74,7 @@ public:
{
}
void InsertTimeRange(const TimeRange& range);
void InsertTimeRange(TimeRange range_to_add);
void RemoveTimeRange(const TimeRange& remove);
+1 -1
View File
@@ -87,7 +87,7 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("AutoSelectDivider"), NodeParam::kBoolean, true);
SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeParam::kBoolean, false);
SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeParam::kBoolean, false);
SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, TimelineWidget::kDWSAsk);
SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, ImportTool::kDWSAsk);
SetEntryInternal(QStringLiteral("Loop"), NodeParam::kBoolean, false);
SetEntryInternal(QStringLiteral("AutoCacheInterval"), NodeParam::kInt, 250);
+1
View File
@@ -70,6 +70,7 @@
OLIVE_NAMESPACE_ENTER
Core* Core::instance_ = nullptr;
const uint Core::kProjectVersion = 201003;
Core::Core(const CoreParams& params) :
main_window_(nullptr),
+2
View File
@@ -273,6 +273,8 @@ public:
*/
bool ValidateFootageInLoadedProject(ProjectPtr project, const QString &project_saved_url);
static const uint kProjectVersion;
public slots:
/**
* @brief Starts an open file dialog to load a project from file
+10 -1
View File
@@ -53,7 +53,7 @@ CrashHandlerDialog::CrashHandlerDialog(const char *report_dir, const char* crash
summary_edit_ = new QTextEdit();
summary_edit_->setPlaceholderText(tr("Describe what you were doing in as much detail as "
"possible. If you can, provide steps to reproduce this crash."));
"possible. If you can, provide steps to reproduce this crash."));
layout->addWidget(summary_edit_);
@@ -166,6 +166,15 @@ void CrashHandlerDialog::ReadProcessFinished()
void CrashHandlerDialog::SendErrorReport()
{
if (summary_edit_->document()->isEmpty()) {
if (QMessageBox::question(this,
tr("No Crash Summary"),
tr("Are you sure you want to send an error report with no crash summary?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
return;
}
}
SetGUIObjectsEnabled(false);
QNetworkAccessManager* manager = new QNetworkAccessManager();
+2 -4
View File
@@ -421,15 +421,13 @@ int ExportDialog::AlignEvenNumber(double d)
ExportParams ExportDialog::GenerateParams() const
{
RenderMode::Mode render_mode = RenderMode::kOnline;
VideoParams video_render_params(static_cast<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue()),
video_tab_->frame_rate_combobox()->GetFrameRate().flipped(),
PixelFormat::instance()->GetConfiguredFormatForMode(render_mode),
PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline),
video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(),
video_tab_->interlaced_combobox()->GetInterlaceMode(),
render_mode);
1);
AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(),
audio_tab_->channel_layout_combobox()->GetChannelLayout(),
+1
View File
@@ -25,6 +25,7 @@ OLIVE_NAMESPACE_ENTER
VolumeNode::VolumeNode()
{
samples_input_ = new NodeInput("samples_in", NodeParam::kSamples);
samples_input_->set_is_keyframable(false);
AddInput(samples_input_);
volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1.0);
+5
View File
@@ -122,6 +122,11 @@ void Block::set_length_and_media_in(const rational &length)
LengthChangedEvent(old_length, length, Timeline::kTrimIn);
}
TimeRange Block::range() const
{
return TimeRange(in(), out());
}
Block *Block::previous()
{
return previous_;
+2
View File
@@ -54,6 +54,8 @@ public:
void set_length_and_media_out(const rational &length);
void set_length_and_media_in(const rational &length);
TimeRange range() const;
Block* previous();
Block* next();
void set_previous(Block* previous);
+16 -7
View File
@@ -20,22 +20,31 @@
#include "edge.h"
#include "input.h"
#include "node.h"
#include "output.h"
OLIVE_NAMESPACE_ENTER
NodeEdge::NodeEdge(NodeOutput *output, NodeInput *input) :
output_(output),
input_(input)
NodeEdge::NodeEdge(NodeOutput *output, NodeInput *input)
{
output_ = ParamToConnection(output);
input_ = ParamToConnection(input);
}
NodeOutput *NodeEdge::output()
NodeOutput *NodeEdge::output() const
{
return output_;
return output_.node->GetOutputWithID(output_.id);
}
NodeInput *NodeEdge::input()
NodeInput *NodeEdge::input() const
{
return input_;
return input_.node->GetInputWithID(input_.id);
}
NodeEdge::Connection NodeEdge::ParamToConnection(NodeParam *param)
{
return {param->parentNode(), param->id()};
}
OLIVE_NAMESPACE_EXIT
+26 -5
View File
@@ -22,13 +22,16 @@
#define EDGE_H
#include <memory>
#include <QString>
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
class NodeOutput;
class Node;
class NodeInput;
class NodeOutput;
class NodeParam;
/**
* @brief A connection between two node parameters (a NodeOutput and a NodeInput)
@@ -44,19 +47,37 @@ public:
*/
NodeEdge(NodeOutput* output, NodeInput* input);
Node* output_node() const
{
return output_.node;
}
Node* input_node() const
{
return input_.node;
}
/**
* @brief Return the output parameter this edge is connected to
*/
NodeOutput* output();
NodeOutput* output() const;
/**
* @brief Return the input parameter this edge is connected to
*/
NodeInput* input();
NodeInput* input() const;
private:
NodeOutput* output_;
NodeInput* input_;
struct Connection {
Node* node;
QString id;
};
static Connection ParamToConnection(NodeParam* param);
Connection output_;
Connection input_;
};
using NodeEdgePtr = std::shared_ptr<NodeEdge>;
+87 -2
View File
@@ -22,6 +22,11 @@
OLIVE_NAMESPACE_ENTER
NodeGraph::NodeGraph() :
operation_stack_(0)
{
}
void NodeGraph::Clear()
{
foreach (Node* node, node_children_) {
@@ -38,14 +43,94 @@ void NodeGraph::AddNode(Node *node)
node->setParent(this);
connect(node, &Node::EdgeAdded, this, &NodeGraph::EdgeAdded);
connect(node, &Node::EdgeRemoved, this, &NodeGraph::EdgeRemoved);
connect(node, &Node::EdgeAdded, this, &NodeGraph::SignalEdgeAdded);
connect(node, &Node::EdgeRemoved, this, &NodeGraph::SignalEdgeRemoved);
node_children_.append(node);
emit NodeAdded(node);
}
void NodeGraph::BeginOperation()
{
operation_stack_++;
}
void NodeGraph::EndOperation()
{
operation_stack_--;
if (!operation_stack_) {
// Signal everything that we cached during the operation
// First, signal the removed edges
foreach (NodeEdgePtr e, cached_removed_edges_) {
emit EdgeRemoved(e);
}
cached_removed_edges_.clear();
// Next, signal the removed nodes
foreach (Node* n, cached_removed_nodes_) {
emit NodeRemoved(n);
}
cached_removed_nodes_.clear();
// Next, signal the added nodes
foreach (Node* n, cached_added_nodes_) {
emit NodeAdded(n);
}
cached_added_nodes_.clear();
// Finally, signal the added edges
foreach (NodeEdgePtr e, cached_added_edges_) {
emit EdgeAdded(e);
}
cached_added_edges_.clear();
}
}
void NodeGraph::SignalNodeAdded(Node* node)
{
if (!operation_stack_) {
emit NodeAdded(node);
} else if (!cached_removed_nodes_.removeOne(node)) {
// If we already removed this node during the operation (appending a signal to
// cached_removed_nodes_), we just remove that instead of appending a new signal. However if we
// didn't (removeOne returning false), only then do we append an add signal
cached_added_nodes_.append(node);
}
}
void NodeGraph::SignalNodeRemoved(Node *node)
{
if (!operation_stack_) {
emit NodeRemoved(node);
} else if (!cached_added_nodes_.removeOne(node)) {
// See SignalNodeAdded() for explanation of this
cached_removed_nodes_.append(node);
}
}
void NodeGraph::SignalEdgeAdded(NodeEdgePtr edge)
{
if (!operation_stack_) {
emit EdgeAdded(edge);
} else if (!cached_removed_edges_.removeOne(edge)) {
// See SignalNodeAdded() for explanation of this
cached_added_edges_.append(edge);
}
}
void NodeGraph::SignalEdgeRemoved(NodeEdgePtr edge)
{
if (!operation_stack_) {
emit EdgeRemoved(edge);
} else if (!cached_added_edges_.removeOne(edge)) {
// See SignalNodeAdded() for explanation of this
cached_removed_edges_.append(edge);
}
}
void NodeGraph::TakeNode(Node *node, QObject* new_parent)
{
if (!ContainsNode(node)) {
+19 -1
View File
@@ -37,7 +37,7 @@ public:
/**
* @brief NodeGraph Constructor
*/
NodeGraph() = default;
NodeGraph();
/**
* @brief Destructively destroys all nodes in the graph
@@ -67,6 +67,10 @@ public:
*/
bool ContainsNode(Node* n) const;
void BeginOperation();
void EndOperation();
signals:
/**
* @brief Signal emitted when a Node is added to the graph
@@ -90,6 +94,20 @@ signals:
private:
QList<Node*> node_children_;
int operation_stack_;
QList<Node*> cached_added_nodes_;
QList<Node*> cached_removed_nodes_;
QList<NodeEdgePtr> cached_added_edges_;
QList<NodeEdgePtr> cached_removed_edges_;
private slots:
void SignalNodeAdded(Node *node);
void SignalNodeRemoved(Node* node);
void SignalEdgeAdded(NodeEdgePtr edge);
void SignalEdgeRemoved(NodeEdgePtr edge);
};
OLIVE_NAMESPACE_EXIT
+5
View File
@@ -27,6 +27,11 @@ Node *AudioInput::copy() const
return new AudioInput();
}
Stream::Type AudioInput::type() const
{
return Stream::kAudio;
}
QString AudioInput::Name() const
{
return tr("Audio Input");
+2
View File
@@ -32,6 +32,8 @@ public:
virtual Node* copy() const override;
virtual Stream::Type type() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
+5
View File
@@ -50,6 +50,11 @@ void MediaInput::SetFootage(StreamPtr f)
footage_input_->set_standard_value(QVariant::fromValue(f));
}
bool MediaInput::IsMedia() const
{
return true;
}
void MediaInput::Retranslate()
{
footage_input_->set_name(tr("Footage"));
+6
View File
@@ -23,6 +23,7 @@
#include "codec/decoder.h"
#include "node/node.h"
#include "project/item/footage/stream.h"
OLIVE_NAMESPACE_ENTER
@@ -35,11 +36,16 @@ class MediaInput : public Node
public:
MediaInput();
virtual Stream::Type type() const = 0;
virtual QList<CategoryID> Category() const override;
StreamPtr footage();
void SetFootage(StreamPtr f);
virtual bool IsMedia() const override;
virtual void Retranslate() override;
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
+5
View File
@@ -36,6 +36,11 @@ Node *VideoInput::copy() const
return new VideoInput();
}
Stream::Type VideoInput::type() const
{
return Stream::kVideo;
}
QString VideoInput::Name() const
{
return tr("Video Input");
+2
View File
@@ -35,6 +35,8 @@ public:
virtual Node* copy() const override;
virtual Stream::Type type() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
+19
View File
@@ -33,6 +33,12 @@ NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QV
{
}
NodeInputArray::~NodeInputArray()
{
// Clear all connected edges (make sure our override is called)
DisconnectAll();
}
bool NodeInputArray::IsArray() const
{
return true;
@@ -58,6 +64,10 @@ void NodeInputArray::SetSize(int size)
if (size < old_size) {
// If the new size is less, delete all extraneous parameters
for (int i=size;i<old_size;i++) {
sub_params_.at(i)->DisconnectAll();
}
for (int i=size;i<old_size;i++) {
delete sub_params_.at(i);
}
@@ -116,6 +126,15 @@ const QVector<NodeInput *> &NodeInputArray::sub_params()
return sub_params_;
}
void NodeInputArray::DisconnectAll()
{
NodeParam::DisconnectAll();
foreach (NodeInput* input, sub_params_) {
input->DisconnectAll();
}
}
void NodeInputArray::InsertAt(int index)
{
// Add another input at the end
+4
View File
@@ -31,6 +31,8 @@ class NodeInputArray : public NodeInput
public:
NodeInputArray(const QString &id, const DataType& type, const QVariant& default_value = 0);
virtual ~NodeInputArray() override;
virtual bool IsArray() const override;
int GetSize() const;
@@ -51,6 +53,8 @@ public:
const QVector<NodeInput*>& sub_params();
virtual void DisconnectAll() override;
signals:
void SizeChanged(int size);
+5
View File
@@ -467,6 +467,11 @@ bool Node::IsTrack() const
return false;
}
bool Node::IsMedia() const
{
return false;
}
const QList<NodeParam *>& Node::parameters() const
{
return params_;
+11
View File
@@ -369,6 +369,15 @@ public:
*/
virtual bool IsTrack() const;
/**
* @brief Returns whether this Node is a "Media" type or not
*
* You shouldn't ever need to override this since all derivatives of Media will automatically have this set to true.
* It's just a more convenient way of checking than dynamic_casting.
*/
virtual bool IsMedia() const;
/**
* @brief The main processing function
*
@@ -568,6 +577,8 @@ QList<T *> Node::FindOutputNode()
return list;
}
using NodePtr = std::shared_ptr<Node>;
OLIVE_NAMESPACE_EXIT
#endif // NODE_H
+6 -15
View File
@@ -55,6 +55,11 @@ TrackOutput::TrackOutput() :
track_height_ = kTrackHeightDefault;
}
TrackOutput::~TrackOutput()
{
DisconnectAll();
}
void TrackOutput::set_track_type(const Timeline::TrackType &track_type)
{
track_type_ = track_type;
@@ -91,15 +96,6 @@ QString TrackOutput::Description() const
"a Sequence.");
}
QString TrackOutput::GetTrackName()
{
if (track_name_.isEmpty()) {
return GetDefaultTrackName(track_type_, index_);
}
return track_name_;
}
const double &TrackOutput::GetTrackHeight() const
{
return track_height_;
@@ -435,11 +431,6 @@ void TrackOutput::Hash(QCryptographicHash &hash, const rational &time) const
}
}
void TrackOutput::SetTrackName(const QString &name)
{
track_name_ = name;
}
void TrackOutput::SetMuted(bool e)
{
muted_input_->set_standard_value(e);
@@ -555,7 +546,7 @@ void TrackOutput::BlockConnected(NodeEdgePtr edge)
void TrackOutput::BlockDisconnected(NodeEdgePtr edge)
{
Block* b = static_cast<Block*>(edge->output()->parentNode());
Block* b = static_cast<Block*>(edge->output_node());
if (block_cache_.contains(b)) {
block_cache_.removeOne(b);
+2 -6
View File
@@ -36,6 +36,8 @@ class TrackOutput : public Node
public:
TrackOutput();
virtual ~TrackOutput() override;
const Timeline::TrackType& track_type() const;
void set_track_type(const Timeline::TrackType& track_type);
@@ -46,8 +48,6 @@ public:
virtual QList<CategoryID> Category() const override;
virtual QString Description() const override;
QString GetTrackName();
const double& GetTrackHeight() const;
void SetTrackHeight(const double& height);
@@ -227,8 +227,6 @@ public:
static const double kTrackHeightInterval;
public slots:
void SetTrackName(const QString& name);
void SetMuted(bool e);
void SetLocked(bool e);
@@ -294,8 +292,6 @@ private:
double track_height_;
QString track_name_;
int index_;
bool locked_;
+16 -6
View File
@@ -134,7 +134,7 @@ TrackOutput* TrackList::AddTrack()
return track;
}
void TrackList::RemoveTrack()
void TrackList::RemoveTrack(QObject* new_parent)
{
if (track_cache_.isEmpty()) {
return;
@@ -144,7 +144,11 @@ void TrackList::RemoveTrack()
GetParentGraph()->TakeNode(track);
delete track;
if (!new_parent) {
delete track;
} else {
track->setParent(new_parent);
}
track_input_->RemoveLast();
}
@@ -184,7 +188,8 @@ void TrackList::TrackConnected(NodeEdgePtr edge)
track_cache_.append(connected_track);
}
connected_track->SetIndex(track_index);
// Update track indexes in the list (including this track)
UpdateTrackIndexesFrom(track_index);
}
connect(connected_track, &TrackOutput::BlockAdded, this, &TrackList::TrackAddedBlock);
@@ -220,9 +225,7 @@ void TrackList::TrackDisconnected(NodeEdgePtr edge)
track_cache_.removeAt(index_of_track);
// Update indices for all subsequent tracks
for (int i=index_of_track; i<track_cache_.size(); i++) {
track_cache_.at(i)->SetIndex(i);
}
UpdateTrackIndexesFrom(index_of_track);
// Traverse through Tracks uncaching and disconnecting them
emit TrackRemoved(track);
@@ -241,6 +244,13 @@ void TrackList::TrackDisconnected(NodeEdgePtr edge)
}
}
void TrackList::UpdateTrackIndexesFrom(int index)
{
for (int i=index; i<track_cache_.size(); i++) {
track_cache_.at(i)->SetIndex(i);
}
}
NodeGraph *TrackList::GetParentGraph() const
{
return static_cast<NodeGraph*>(parent()->parent());
+3 -1
View File
@@ -44,7 +44,7 @@ public:
TrackOutput *AddTrack();
void RemoveTrack();
void RemoveTrack(QObject *new_parent);
const rational& GetTotalLength() const;
@@ -68,6 +68,8 @@ signals:
void TrackHeightChanged(int index, int height);
private:
void UpdateTrackIndexesFrom(int index);
/**
* @brief A cache of connected Tracks
*/
+38 -2
View File
@@ -52,7 +52,7 @@ ViewerOutput::ViewerOutput() :
connect(list, &TrackList::TrackListChanged, this, &ViewerOutput::UpdateTrackCache);
connect(list, &TrackList::LengthChanged, this, &ViewerOutput::VerifyLength);
connect(list, &TrackList::BlockAdded, this, &ViewerOutput::TrackListAddedBlock);
connect(list, &TrackList::BlockRemoved, this, &ViewerOutput::BlockRemoved);
connect(list, &TrackList::BlockRemoved, this, &ViewerOutput::SignalBlockRemoved);
connect(list, &TrackList::TrackAdded, this, &ViewerOutput::TrackListAddedTrack);
connect(list, &TrackList::TrackRemoved, this, &ViewerOutput::TrackRemoved);
connect(list, &TrackList::TrackHeightChanged, this, &ViewerOutput::TrackHeightChangedSlot);
@@ -62,6 +62,11 @@ ViewerOutput::ViewerOutput() :
uuid_ = QUuid::createUuid();
}
ViewerOutput::~ViewerOutput()
{
DisconnectAll();
}
Node *ViewerOutput::copy() const
{
return new ViewerOutput();
@@ -274,6 +279,27 @@ void ViewerOutput::set_media_name(const QString &name)
emit MediaNameChanged(media_name_);
}
void ViewerOutput::SignalBlockAdded(Block *block, const TrackReference& track)
{
if (!operation_stack_) {
emit BlockAdded(block, track);
} else {
cached_block_removed_.removeOne(block);
cached_block_added_.insert(block, track);
}
}
void ViewerOutput::SignalBlockRemoved(Block *block)
{
if (!operation_stack_) {
emit BlockRemoved({block});
} else {
// We keep track of all blocks that are removed, even if we don't end up signalling them
cached_block_added_.remove(block);
cached_block_removed_.append(block);
}
}
void ViewerOutput::BeginOperation()
{
operation_stack_++;
@@ -285,13 +311,23 @@ void ViewerOutput::EndOperation()
{
operation_stack_--;
if (!operation_stack_) {
for (auto it=cached_block_added_.cbegin(); it!=cached_block_added_.cend(); it++) {
emit BlockAdded(it.key(), it.value());
}
cached_block_added_.clear();
emit BlockRemoved(cached_block_removed_);
cached_block_removed_.clear();
}
Node::EndOperation();
}
void ViewerOutput::TrackListAddedBlock(Block *block, int index)
{
Timeline::TrackType type = static_cast<TrackList*>(sender())->type();
emit BlockAdded(block, TrackReference(type, index));
SignalBlockAdded(block, TrackReference(type, index));
}
void ViewerOutput::TrackListAddedTrack(TrackOutput *track)
+9 -1
View File
@@ -47,6 +47,8 @@ class ViewerOutput : public Node
public:
ViewerOutput();
virtual ~ViewerOutput() override;
virtual Node* copy() const override;
virtual QString Name() const override;
@@ -139,7 +141,7 @@ signals:
void AudioParamsChanged();
void BlockAdded(Block* block, TrackReference track);
void BlockRemoved(Block* block);
void BlockRemoved(const QList<Block*>& blocks);
void TrackAdded(TrackOutput* track, Timeline::TrackType type);
void TrackRemoved(TrackOutput* track);
@@ -149,6 +151,9 @@ signals:
void MediaNameChanged(const QString& name);
private:
QMap<Block*, TrackReference> cached_block_added_;
QList<Block*> cached_block_removed_;
QUuid uuid_;
NodeInput* texture_input_;
@@ -186,6 +191,9 @@ private slots:
void TrackHeightChangedSlot(int index, int height);
void SignalBlockAdded(Block *block, const TrackReference &track);
void SignalBlockRemoved(Block *block);
};
OLIVE_NAMESPACE_EXIT
+2 -4
View File
@@ -43,9 +43,7 @@ NodeParam::NodeParam(const QString &id) :
NodeParam::~NodeParam()
{
// Clear all connected edges
while (!edges_.isEmpty()) {
DisconnectEdge(edges_.last());
}
DisconnectAll();
}
const QString NodeParam::id() const
@@ -111,7 +109,7 @@ const QVector<NodeEdgePtr> &NodeParam::edges()
void NodeParam::DisconnectAll()
{
while (!edges_.isEmpty()) {
DisconnectEdge(edges_.first());
DisconnectEdge(edges_.last());
}
}
+1 -1
View File
@@ -319,7 +319,7 @@ public:
/**
* @brief Disconnect any edges connecting this parameter to other parameters
*/
void DisconnectAll();
virtual void DisconnectAll();
/**
* @brief Connect an output parameter to an input parameter
+2 -16
View File
@@ -32,21 +32,14 @@ CurvePanel::CurvePanel(QWidget *parent) :
Retranslate();
}
NodeInput *CurvePanel::GetInput() const
{
return static_cast<CurveWidget*>(GetTimeBasedWidget())->GetInput();
}
void CurvePanel::DeleteSelected()
{
static_cast<CurveWidget*>(GetTimeBasedWidget())->DeleteSelected();
}
void CurvePanel::SetInput(NodeInput *input)
void CurvePanel::SetNodes(const QList<Node *> &nodes)
{
static_cast<CurveWidget*>(GetTimeBasedWidget())->SetInput(input);
Retranslate();
static_cast<CurveWidget*>(GetTimeBasedWidget())->SetNodes(nodes);
}
void CurvePanel::IncreaseTrackHeight()
@@ -66,13 +59,6 @@ void CurvePanel::Retranslate()
TimeBasedPanel::Retranslate();
SetTitle(tr("Curve Editor"));
NodeInput* connected_input = static_cast<CurveWidget*>(GetTimeBasedWidget())->GetInput();
if (connected_input) {
SetSubtitle(connected_input->name());
} else {
SetSubtitle(QString());
}
}
OLIVE_NAMESPACE_EXIT
+1 -3
View File
@@ -32,12 +32,10 @@ class CurvePanel : public TimeBasedPanel
public:
CurvePanel(QWidget* parent);
NodeInput* GetInput() const;
virtual void DeleteSelected() override;
public slots:
void SetInput(NodeInput* input);
void SetNodes(const QList<Node*>& nodes);
virtual void IncreaseTrackHeight() override;
+2 -75
View File
@@ -28,9 +28,9 @@ ParamPanel::ParamPanel(QWidget* parent) :
TimeBasedPanel(QStringLiteral("ParamPanel"), parent)
{
NodeParamView* view = new NodeParamView();
connect(view, &NodeParamView::InputDoubleClicked, this, &ParamPanel::CreateCurvePanel);
connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode);
//connect(view, &NodeParamView::FoundGizmos, this, &ParamPanel::FoundGizmos);
connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged);
connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged);
SetTimeBasedWidget(view);
Retranslate();
@@ -50,14 +50,6 @@ void ParamPanel::DeselectNodes(const QList<Node *> &nodes)
Retranslate();
}
void ParamPanel::SetTimestamp(const int64_t &timestamp)
{
TimeBasedPanel::SetTimestamp(timestamp);
// Ensure all CurvePanels are updated with this time too
ParamViewTimeChanged(timestamp);
}
void ParamPanel::DeleteSelected()
{
static_cast<NodeParamView*>(GetTimeBasedWidget())->DeleteSelected();
@@ -78,69 +70,4 @@ void ParamPanel::Retranslate()
}
}
void ParamPanel::CreateCurvePanel(NodeInput *input)
{
if (!input->is_keyframable()) {
return;
}
CurvePanel* panel = open_curve_panels_.value(input);
if (panel) {
panel->raise();
return;
}
NodeParamView* view = static_cast<NodeParamView*>(GetTimeBasedWidget());
panel = Core::instance()->main_window()->AppendCurvePanel();
panel->ConnectViewerNode(view->GetConnectedNode());
panel->SetTimestamp(view->GetTimestamp());
panel->SetInput(input);
connect(view, &NodeParamView::TimeChanged, this, &ParamPanel::ParamViewTimeChanged);
connect(panel, &CurvePanel::TimeChanged, this, &ParamPanel::CurvePanelTimeChanged);
connect(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel);
open_curve_panels_.insert(input, panel);
}
void ParamPanel::ClosingCurvePanel()
{
CurvePanel* panel = static_cast<CurvePanel*>(sender());
open_curve_panels_.remove(panel->GetInput());
}
void ParamPanel::ParamViewTimeChanged(const int64_t &time)
{
// Ensure all CurvePanels are updated with this time too
QHash<NodeInput*, CurvePanel*>::const_iterator i;
for (i=open_curve_panels_.begin(); i!=open_curve_panels_.end(); i++) {
// If connected viewers are the same, set the timestamp
if (i.value()->GetConnectedViewer() == GetConnectedViewer()) {
i.value()->SetTimestamp(time);
}
}
}
void ParamPanel::CurvePanelTimeChanged(const int64_t &time)
{
GetTimeBasedWidget()->SetTimestamp(time);
emit GetTimeBasedWidget()->TimeChanged(time);
CurvePanel* src = static_cast<CurvePanel*>(sender());
// Ensure all CurvePanels are updated with this time too
QHash<NodeInput*, CurvePanel*>::const_iterator i;
for (i=open_curve_panels_.begin(); i!=open_curve_panels_.end(); i++) {
// If connected viewers are the same and the panel isn't the source, set the timestamp
if (i.value() != src && i.value()->GetConnectedViewer() == src->GetConnectedViewer()) {
i.value()->SetTimestamp(time);
}
}
}
OLIVE_NAMESPACE_EXIT
+3 -16
View File
@@ -37,31 +37,18 @@ public slots:
void SelectNodes(const QList<Node*>& nodes);
void DeselectNodes(const QList<Node*>& nodes);
virtual void SetTimestamp(const int64_t& timestamp) override;
virtual void DeleteSelected() override;
signals:
void RequestSelectNode(const QList<Node*>& target);
void FoundGizmos(Node* node);
void NodeOrderChanged(const QList<Node*>& nodes);
void FocusedNodeChanged(Node* n);
protected:
virtual void Retranslate() override;
private slots:
void CreateCurvePanel(NodeInput* input);
void ClosingCurvePanel();
private:
QHash<NodeInput*, CurvePanel*> open_curve_panels_;
private slots:
void ParamViewTimeChanged(const int64_t& time);
void CurvePanelTimeChanged(const int64_t& time);
};
OLIVE_NAMESPACE_EXIT
+1
View File
@@ -25,6 +25,7 @@
#include <QUrl>
#include "core.h"
#include "node/input/media/media.h"
OLIVE_NAMESPACE_ENTER
+1
View File
@@ -25,6 +25,7 @@
#include "project.h"
#include "undo/undocommand.h"
#include "node/block/block.h"
OLIVE_NAMESPACE_ENTER
+380 -130
View File
@@ -28,11 +28,20 @@
OLIVE_NAMESPACE_ENTER
const rational AudioPlaybackCache::kDefaultSegmentSize = 5;
AudioPlaybackCache::AudioPlaybackCache(QObject* parent) :
PlaybackCache(parent)
{
quint32 r = std::rand();
UpdateFilename(QString::number(r));
}
AudioPlaybackCache::~AudioPlaybackCache()
{
// Segments are volatile, so delete them here
foreach (const Segment& s, segments_) {
QFile::remove(s.filename());
}
segments_.clear();
}
void AudioPlaybackCache::SetParameters(const AudioParams &params)
@@ -44,16 +53,10 @@ void AudioPlaybackCache::SetParameters(const AudioParams &params)
params_ = params;
// Restart empty file so there's always "something" to play
QFile f(filename_);
if (f.open(QFile::WriteOnly)) {
f.close();
}
segments_.clear();
// Our current audio cache is unusable, so we truncate it automatically
TimeRange invalidate_range(0, GetLength());
if (invalidate_range.in() != invalidate_range.out()) {
Invalidate(invalidate_range);
}
InvalidateAll();
emit ParametersChanged();
}
@@ -65,148 +68,215 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
return;
}
QFile f(filename_);
if (f.open(QFile::ReadWrite)) {
QByteArray a = samples->toPackedData();
// Determine if we have enough segments to pull this off
while (segment_length_ < range.out()) {
rational seg_sz = qMin(kDefaultSegmentSize, range.out() - segment_length_);
segments_.push_back(CreateSegment(seg_sz));
segment_length_ += seg_sz;
}
foreach (const TimeRange& r, valid_ranges) {
// Calculate destination offsets
qint64 start_offset = params_.time_to_bytes(r.in());
qint64 max_len = params_.time_to_bytes(r.out());
QByteArray a;
if (samples) {
// Convert to packed data, which is what we store on disk
a = samples->toPackedData();
}
if (f.size() < max_len) {
f.resize(max_len);
// Keep track of validated ranges so we can signal them all at once at the end
TimeRangeList ranges_we_validated;
// Write each valid range to the segments
foreach (const TimeRange& r, valid_ranges) {
rational this_segment_in = 0;
for (auto it=segments_.begin(); it!=segments_.end(); it++) {
rational this_segment_out = this_segment_in + (*it).length();
if (r.in() < this_segment_out) {
// We'll write at least something to this segment
QFile seg_file((*it).filename());
if (seg_file.open(QFile::ReadWrite)) {
// Calculate how much to write
rational this_write_in_point = qMax(r.in(), this_segment_in);
rational this_write_out_point = qMin(r.out(), this_segment_out);
// Calculate what the byte offsets are going to be in this segment file
rational in_point_relative = this_write_in_point - this_segment_in;
qint64 dst_offset = params_.time_to_bytes(in_point_relative);
// Calculate where to retrieve data from in the source buffer
qint64 src_offset = params_.time_to_bytes(this_write_in_point - range.in());
// Determine how many bytes need to be written
qint64 total_write_length = params_.time_to_bytes(this_write_out_point - this_write_in_point);
// Determine how many bytes we actually have in the source buffer
qint64 possible_write_length = qMin(qMax(qint64(0), a.size() - src_offset), total_write_length);
// Seek to our start offset
seg_file.seek(dst_offset);
// If we have source bytes to write, write them here
if (possible_write_length > 0) {
seg_file.write(a.data() + src_offset, possible_write_length);
}
if (possible_write_length < total_write_length) {
// Fill remaining space with silence
QByteArray s(total_write_length - possible_write_length, 0x00);
seg_file.write(s);
}
seg_file.close();
ranges_we_validated.InsertTimeRange(TimeRange(this_write_in_point, this_write_out_point));
} else {
qWarning() << "Failed to write PCM data to" << seg_file.fileName();
}
}
// Calculate source offsets
qint64 sample_start = params_.time_to_bytes(r.in() - range.in());
qint64 sample_len = params_.time_to_bytes(r.length());
qint64 actual_write = qMin(sample_len, a.size() - sample_start);
f.seek(start_offset);
f.write(a.data() + sample_start, actual_write);
if (actual_write < sample_len) {
// Fill remaining space with silence
QByteArray s(sample_len - actual_write, 0x00);
f.write(s);
if (r.out() <= this_segment_out) {
// We've reached the end of this range, we can break out of the loop here
break;
}
// Each segment is contiguous, so this out will be the next segment's in
this_segment_in = this_segment_out;
}
}
f.close();
Validate(range);
} else {
qWarning() << "Failed to write PCM data to" << filename_;
foreach (const TimeRange& v, ranges_we_validated) {
Validate(v);
}
}
void AudioPlaybackCache::WriteSilence(const TimeRange &range)
void AudioPlaybackCache::WriteSilence(const TimeRange &range, qint64 job_time)
{
QFile f(filename_);
if (f.open(QFile::ReadWrite)) {
qint64 start_offset = params_.time_to_bytes(range.in());
qint64 max_len = params_.time_to_bytes(range.out());
qint64 write_len = max_len - start_offset;
if (f.size() < max_len) {
f.resize(max_len);
}
f.seek(start_offset);
QByteArray a(write_len, 0x00);
f.write(a);
f.close();
Validate(range);
} else {
qWarning() << "Failed to write PCM data to" << filename_;
}
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
// it an empty sample buffer
WritePCM(range, nullptr, job_time);
}
/*
void AudioPlaybackCache::SetUuid(const QUuid &id)
{
UpdateFilename(id.toByteArray());
}
*/
void AudioPlaybackCache::ShiftEvent(const rational &from, const rational &to)
{
qint64 from_offset = params_.time_to_bytes(from);
qint64 to_offset = params_.time_to_bytes(to);
if (from_offset == to_offset) {
if (from == to) {
return;
}
QFile f(filename_);
if (f.open(QFile::ReadWrite)) {
if (!f.size()) {
return;
}
int to_index = -1;
int from_index = -1;
rational to_start, from_start;
qint64 chunk = qAbs(to_offset - from_offset);
{
rational seg_start;
QByteArray buf(chunk, Qt::Uninitialized);
// Find which segments intersect with this shift event
for (int i=0; i<segments_.size(); i++) {
const Segment& seg = segments_.at(i);
if (to > from) {
// Shifting forwards, we must insert a new region and shift all the bytes there
// For shifting forwards, we copy bytes starting at the back so that bytes we need don't
// get overwritten.
qint64 read_offset = f.size();
f.resize(f.size() + chunk);
qint64 write_offset = f.size();
while (read_offset != from_offset) {
// Calculate how much will be read this time
qint64 chunk_sz = qMin(chunk, read_offset - from_offset);
read_offset -= chunk_sz;
// Read that chunk
f.seek(read_offset);
f.read(buf.data(), chunk_sz);
// Write it at the destination
write_offset -= chunk_sz;
f.seek(write_offset);
f.write(buf.data(), chunk_sz);
rational seg_end = seg_start + seg.length();
if (to_index == -1 && seg_end > to) {
to_index = i;
to_start = seg_start;
}
// Replace remainder with silence
f.seek(from_offset);
buf.fill(0);
f.write(buf);
if (from_index == -1 && seg_end >= from) {
from_index = i;
from_start = seg_start;
}
if (to_index != -1 && from_index != -1) {
break;
}
seg_start = seg_end;
}
}
const rational& from_length = segments_[from_index].length();
rational from_end = from_start + from_length;
if (from < to) {
// Shifting forwards, we must insert a new region and split a segment in half if necessary
int insert_index;
// Determine at what part of the array we'll be insert into
if (from == from_start) {
insert_index = from_index;
} else {
// Shifting backwards, we will shift bytes and truncate
while (from_offset != f.size()) {
// Read region to be shifted
f.seek(from_offset);
qint64 read_sz = f.read(buf.data(), buf.size());
from_offset += read_sz;
// Write it at the destination
f.seek(to_offset);
to_offset += f.write(buf, read_sz);
}
// Truncate
f.resize(f.size() - chunk);
insert_index = from_index + 1;
}
f.close();
if (from < from_end) {
// Split from segment into two
Segment second = CloneSegment(segments_.at(from_index));
TrimSegmentOut(&segments_[from_index], from - from_start);
TrimSegmentIn(&second, from_end - from);
segments_.insert(insert_index, second);
}
// Insert silent segments
rational time_to_insert = to - from;
rational inserted_time;
while (inserted_time < time_to_insert) {
rational new_seg_sz = qMin(kDefaultSegmentSize, time_to_insert - inserted_time);
segments_.insert(insert_index, CreateSegment(new_seg_sz));
inserted_time += new_seg_sz;
}
segment_length_ += time_to_insert;
} else {
qWarning() << "Failed to write PCM data to" << filename_;
// Shifting backwards, we'll be removing segments and truncating them if necessary
const rational& to_length = segments_.at(to_index).length();
rational to_end = to_start + to_length;
if (from_index == to_index) {
// Shift occurs in the same segment
if (to > to_start && from < to_end) {
// Split into two and process as normal
Segment second = CloneSegment(segments_.at(to_index));
from_index++;
segments_.insert(from_index, second);
} else if (to == to_start && from == to_end) {
RemoveSegmentFromArray(to_index);
} else if (to == to_start) {
TrimSegmentIn(&segments_[to_index], to_end - from);
} else {
TrimSegmentOut(&segments_[from_index], to - to_start);
}
} else {
// Remove all central segments (if there are any)
while (from_index > to_index + 1) {
RemoveSegmentFromArray(to_index + 1);
from_index--;
}
}
if (from_index != to_index) {
// Remove or trim "to" segment
if (to == to_start) {
RemoveSegmentFromArray(to_index);
from_index--;
} else if (to < to_end) {
TrimSegmentOut(&segments_[to_index], to - to_start);
}
// Remove or trim "from" segment
if (from == from_end) {
RemoveSegmentFromArray(from_index);
} else if (from > from_start) {
TrimSegmentIn(&segments_[from_index], from_end - from);
}
}
segment_length_ -= (from - to);
}
}
@@ -216,11 +286,96 @@ void AudioPlaybackCache::LengthChangedEvent(const rational& old, const rational&
return;
}
if (newlen < old) {
QFile(filename_).resize(params_.time_to_bytes(newlen));
while (newlen < segment_length_) {
Segment& last_seg = segments_.back();
if (segment_length_ - last_seg.length() < newlen) {
// Truncate this segment rather than removing it
rational diff = segment_length_ - newlen;
TrimSegmentOut(&last_seg, last_seg.length() - diff);
segment_length_ -= diff;
} else {
// Remove last segment
segment_length_ -= last_seg.length();
RemoveSegmentFromArray(segments_.size() - 1);
}
}
}
AudioPlaybackCache::Segment AudioPlaybackCache::CloneSegment(const AudioPlaybackCache::Segment &s) const
{
Segment new_seg = s;
// Copy data to a new file
QString new_filename = GenerateSegmentFilename();
QFile::copy(s.filename(), new_filename);
new_seg.set_filename(new_filename);
return new_seg;
}
AudioPlaybackCache::Segment AudioPlaybackCache::CreateSegment(const rational &length) const
{
Segment s(length, GenerateSegmentFilename());
// Create empty file
QFile f(s.filename());
if (f.open(QFile::WriteOnly)) {
f.close();
}
return s;
}
QString AudioPlaybackCache::GenerateSegmentFilename() const
{
QString new_seg_filename;
do {
uint32_t r = std::rand();
new_seg_filename = QDir(GetCacheDirectory()).filePath(QStringLiteral("%1.pcm").arg(r));
} while (QFileInfo::exists(new_seg_filename));
return new_seg_filename;
}
void AudioPlaybackCache::TrimSegmentIn(AudioPlaybackCache::Segment *s, const rational &new_length)
{
// Read filename
QFile f(s->filename());
if (f.open(QFile::ReadWrite)) {
// Read whole segment into memory
QByteArray data = f.readAll();
// Trim to new length
data = data.right(params_.time_to_bytes(new_length));
// Clear existing file
f.resize(0);
// Write trimmed data
f.write(data);
f.close();
}
s->set_length(new_length);
}
void AudioPlaybackCache::TrimSegmentOut(AudioPlaybackCache::Segment *s, const rational &new_length)
{
QFile(s->filename()).resize(params_.time_to_bytes(new_length));
s->set_length(new_length);
}
void AudioPlaybackCache::RemoveSegmentFromArray(int index)
{
QFile::remove(segments_.at(index).filename());
segments_.removeAt(index);
}
QList<TimeRange> AudioPlaybackCache::GetValidRanges(const TimeRange& range, const qint64& job_time)
{
QList<TimeRange> valid_ranges;
@@ -236,15 +391,110 @@ QList<TimeRange> AudioPlaybackCache::GetValidRanges(const TimeRange& range, cons
return valid_ranges;
}
void AudioPlaybackCache::UpdateFilename(const QString &s)
AudioPlaybackCache::PlaybackDevice *AudioPlaybackCache::CreatePlaybackDevice(QObject* parent) const
{
filename_ = QDir(GetCacheDirectory()).filePath(s);
filename_.append(QStringLiteral(".pcm"));
return new PlaybackDevice(segments_, parent);
}
const QString &AudioPlaybackCache::GetPCMFilename() const
AudioPlaybackCache::Segment::Segment(const rational &length, const QString &s)
{
return filename_;
length_ = length;
filename_ = s;
}
AudioPlaybackCache::PlaybackDevice::PlaybackDevice(const AudioPlaybackCache::Playlist &playlist, QObject *parent) :
QIODevice(parent),
playlist_(playlist),
current_segment_(0),
segment_read_index_(0)
{
}
AudioPlaybackCache::PlaybackDevice::~PlaybackDevice()
{
close();
}
bool AudioPlaybackCache::PlaybackDevice::seek(qint64 pos)
{
// Default behavior
QIODevice::seek(pos);
// Find which segment we're in
// FIXME: Inefficient
qint64 seg_start_bytes = 0;
for (int i=0; i<playlist_.size(); i++) {
const Segment& s = playlist_.at(i);
qint64 this_seg_sz = QFile(s.filename()).size();
qint64 seg_end_bytes = seg_start_bytes + this_seg_sz;
if (pos < seg_end_bytes) {
// This must be the segment we're looking for
current_segment_ = i;
segment_read_index_ = pos - seg_start_bytes;
// Succeeded at seeking to this position
return true;
}
seg_start_bytes = seg_end_bytes;
}
// Couldn't find this position in the file
return false;
}
qint64 AudioPlaybackCache::PlaybackDevice::size() const
{
qint64 p = 0;
// FIXME: Inefficient
for (int i=0; i<playlist_.size(); i++) {
p += QFile(playlist_.at(i).filename()).size();
}
return p;
}
qint64 AudioPlaybackCache::PlaybackDevice::readData(char *data, qint64 maxSize)
{
qint64 read_size = 0;
while (read_size < maxSize && current_segment_ < playlist_.size()) {
QFile segment_file(playlist_.at(current_segment_).filename());
if (segment_file.open(QFile::ReadOnly)) {
// Seek to our stored index of this segment
segment_file.seek(segment_read_index_);
// Determine how many bytes to read
qint64 this_read_length = qMin(segment_file.size() - segment_read_index_,
maxSize - read_size);
// Read those bytes
segment_file.read(data + read_size, this_read_length);
// Close the file
segment_file.close();
// Add to the read index
segment_read_index_ += this_read_length;
// Add to the read size
read_size += this_read_length;
// If we've reached the end of this segment, tick the counter over to the next segment
if (segment_read_index_ == segment_file.size()) {
// Jump to the next file
segment_read_index_ = 0;
current_segment_++;
}
} else {
qWarning() << "Failed to read data from segment";
}
}
return read_size;
}
OLIVE_NAMESPACE_EXIT
+91 -7
View File
@@ -33,6 +33,8 @@ class AudioPlaybackCache : public PlaybackCache
public:
AudioPlaybackCache(QObject* parent = nullptr);
virtual ~AudioPlaybackCache() override;
AudioParams GetParameters()
{
return params_;
@@ -42,14 +44,82 @@ public:
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const qint64& job_time);
void WriteSilence(const TimeRange &range);
//void SetUuid(const QUuid& id);
const QString& GetPCMFilename() const;
void WriteSilence(const TimeRange &range, qint64 job_time);
QList<TimeRange> GetValidRanges(const TimeRange &range, const qint64 &job_time);
class Segment
{
public:
Segment() = default;
Segment(const rational& length, const QString& s);
const rational& length() const
{
return length_;
}
void set_length(const rational& length)
{
length_ = length;
}
const QString& filename() const
{
return filename_;
}
void set_filename(const QString& filename)
{
filename_ = filename;
}
private:
QString filename_;
rational length_;
};
using Playlist = QVector<Segment>;
class PlaybackDevice : public QIODevice
{
public:
PlaybackDevice(const Playlist& playlist, QObject* parent = nullptr);
virtual ~PlaybackDevice() override;
virtual bool isSequential() const override
{
return false;
}
virtual bool seek(qint64 pos) override;
virtual qint64 size() const override;
virtual qint64 readData(char *data, qint64 maxSize) override;
virtual qint64 writeData(const char *data, qint64 maxSize) override
{
Q_UNUSED(data)
Q_UNUSED(maxSize)
return -1;
}
private:
Playlist playlist_;
int current_segment_;
qint64 segment_read_index_;
};
PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const;
signals:
void ParametersChanged();
@@ -59,9 +129,23 @@ protected:
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;
private:
void UpdateFilename(const QString& s);
static const rational kDefaultSegmentSize;
QString filename_;
Segment CloneSegment(const Segment& s) const;
Segment CreateSegment(const rational& length) const;
QString GenerateSegmentFilename() const;
void TrimSegmentIn(Segment* s, const rational& new_length);
void TrimSegmentOut(Segment* s, const rational& new_length);
void RemoveSegmentFromArray(int index);
Playlist segments_;
rational segment_length_;
AudioParams params_;
+2 -1
View File
@@ -22,6 +22,7 @@
#include <QDir>
#include <QFloat16>
#include <QStandardPaths>
#include "common/define.h"
#include "common/filefunctions.h"
@@ -82,7 +83,7 @@ void ColorManager::SetUpDefaultConfig()
}
// Extract OCIO config - kind of hacky, but it'll work
QString dir = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("ocioconf"));
QString dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(QStringLiteral("ocioconf"));
FileFunctions::CopyDirectory(QStringLiteral(":/ocioconf"),
dir,
+1 -1
View File
@@ -108,7 +108,7 @@ bool ExportTask::Run()
if (params_.audio_enabled()) {
// Write audio data now
encoder_->WriteAudio(audio_params(), audio_data_.GetPCMFilename());
encoder_->WriteAudio(audio_params(), audio_data_.CreatePlaybackDevice(encoder_));
}
encoder_->Close();
+17 -1
View File
@@ -25,6 +25,7 @@
#include <QXmlStreamReader>
#include "common/xmlutils.h"
#include "core.h"
OLIVE_NAMESPACE_ENTER
@@ -45,7 +46,17 @@ bool ProjectLoadTask::Run()
if (reader.name() == QStringLiteral("olive")) {
while(XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("version")) {
qDebug() << "Project version:" << reader.readElementText();
uint project_version = reader.readElementText().toUInt();
if (project_version > Core::kProjectVersion) {
// Project is newer than we support
SetError(tr("This project is newer than this version of Olive and cannot be opened."));
return false;
} else if (project_version < 201003) { // Change this if we drop support for a project version
// Project is older than we support
SetError(tr("This project is from a version of Olive that is no longer supported in this version."));
return false;
}
} else if (reader.name() == QStringLiteral("url")) {
project_saved_url_ = reader.readElementText();
} else if (reader.name() == QStringLiteral("project")) {
@@ -62,6 +73,11 @@ bool ProjectLoadTask::Run()
reader.skipCurrentElement();
}
}
} else if (reader.name() == QStringLiteral("project")) {
// 0.1 projects use "project" as the root instead of Olive. We don't currently support
// these projects
SetError(tr("This project is from a version of Olive that is no longer supported in this version."));
return false;
} else {
reader.skipCurrentElement();
}
+4 -1
View File
@@ -25,6 +25,7 @@
#include <QXmlStreamWriter>
#include "common/filefunctions.h"
#include "core.h"
OLIVE_NAMESPACE_ENTER
@@ -49,7 +50,9 @@ bool ProjectSaveTask::Run()
writer.writeStartElement("olive");
writer.writeTextElement("version", "0.2.0");
// Version is stored in YYMMDD from whenever the project format was last changed
// Allows easy integer math for checking project versions.
writer.writeTextElement("version", QString::number(Core::kProjectVersion));
writer.writeTextElement("url", project_->filename());
+2 -2
View File
@@ -122,7 +122,7 @@ void RenderTask::Render(const TimeRangeList& video_range,
|| !download_futures.empty()
|| !audio_lookup_table.empty())) {
if (!IsCancelled() && !frame_queue.empty()) {
while (!IsCancelled() && !frame_queue.empty()) {
// Pop another frame off the frame queue
const HashTimePair& p = frame_queue.front();
@@ -168,7 +168,7 @@ void RenderTask::Render(const TimeRangeList& video_range,
frame_queue.pop_front();
}
if (!IsCancelled() && !audio_queue.empty()) {
while (!IsCancelled() && !audio_queue.empty()) {
audio_lookup_table.push_back({audio_queue.front(), backend_->RenderAudio(audio_queue.front())});
audio_queue.pop_front();
}
+5
View File
@@ -49,6 +49,11 @@ bool TrackReference::operator==(const TrackReference &ref) const
return type_ == ref.type_ && index_ == ref.index_;
}
bool TrackReference::operator!=(const TrackReference &ref) const
{
return !(*this == ref);
}
uint qHash(const TrackReference &r, uint seed)
{
// Not super efficient, but couldn't think of any better way to ensure a different hash each time
+3
View File
@@ -40,10 +40,13 @@ public:
bool operator==(const TrackReference& ref) const;
bool operator!=(const TrackReference& ref) const;
private:
Timeline::TrackType type_;
int index_;
};
uint qHash(const TrackReference& r, uint seed);
+3 -2
View File
@@ -29,9 +29,10 @@ add_subdirectory(manageddisplay)
add_subdirectory(menu)
add_subdirectory(nodecombobox)
add_subdirectory(nodecopypaste)
add_subdirectory(nodeview)
add_subdirectory(nodeparamview)
add_subdirectory(nodetableview)
add_subdirectory(nodetreeview)
add_subdirectory(nodeparamview)
add_subdirectory(nodeview)
add_subdirectory(panel)
add_subdirectory(path)
add_subdirectory(pixelsampler)
+15 -19
View File
@@ -35,6 +35,7 @@ const int kMaximumSmoothness = 8;
AudioMonitor::AudioMonitor(QWidget *parent) :
QOpenGLWidget(parent),
file_(nullptr),
cached_channels_(0)
{
values_.resize(kMaximumSmoothness);
@@ -45,11 +46,6 @@ AudioMonitor::AudioMonitor(QWidget *parent) :
connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop);
}
AudioMonitor::~AudioMonitor()
{
Stop();
}
void AudioMonitor::SetParams(const AudioParams &params)
{
params_ = params;
@@ -63,18 +59,19 @@ void AudioMonitor::SetParams(const AudioParams &params)
peaked_.fill(false);
}
void AudioMonitor::OutputDeviceSet(const QString &filename, qint64 offset, int playback_speed)
void AudioMonitor::OutputDeviceSet(AudioPlaybackCache *cache, qint64 offset, int playback_speed)
{
Stop();
file_.setFileName(filename);
file_ = cache->CreatePlaybackDevice(this);
if (!file_.open(QFile::ReadOnly)) {
qWarning() << "Failed to open" << filename;
if (!file_->open(QFile::ReadOnly)) {
qWarning() << "Failed to open IO device for AudioMonitor display";
Stop();
return;
}
file_.seek(offset);
file_->seek(offset);
playback_speed_ = playback_speed;
@@ -85,9 +82,8 @@ void AudioMonitor::OutputDeviceSet(const QString &filename, qint64 offset, int p
void AudioMonitor::Stop()
{
if (file_.isOpen()) {
file_.close();
}
delete file_;
file_ = nullptr;
}
void AudioMonitor::OutputPushed(const QByteArray &d)
@@ -215,7 +211,7 @@ void AudioMonitor::paintGL()
QVector<double> v(params_.channel_count(), 0);
if (file_.isOpen()) {
if (file_) {
UpdateValuesFromFile(v);
}
@@ -258,7 +254,7 @@ void AudioMonitor::paintGL()
}
}
if (all_zeroes && !file_.isOpen()) {
if (all_zeroes && !file_) {
// Optimize by disabling the update loop
SetUpdateLoop(false);
}
@@ -287,18 +283,18 @@ void AudioMonitor::UpdateValuesFromFile(QVector<double>& v)
if (playback_speed_ < 0) {
// If reversing, jump back by the amount of bytes we're going to read
bytes_to_read = qMin(bytes_to_read, file_.pos());
bytes_to_read = qMin(bytes_to_read, file_->pos());
file_.seek(file_.pos() - bytes_to_read);
file_->seek(file_->pos() - bytes_to_read);
}
// Read bytes in from file
QByteArray b = file_.read(bytes_to_read);
QByteArray b = file_->read(bytes_to_read);
if (playback_speed_ < 0) {
// If reversing, head back to where we were before the read so that the next read starts
// from where we left off
file_.seek(file_.pos() - bytes_to_read);
file_->seek(file_->pos() - bytes_to_read);
}
// If speed is not 1, transform it here
+3 -4
View File
@@ -27,6 +27,7 @@
#include "common/define.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
OLIVE_NAMESPACE_ENTER
@@ -36,12 +37,10 @@ class AudioMonitor : public QOpenGLWidget
public:
AudioMonitor(QWidget* parent = nullptr);
virtual ~AudioMonitor() override;
public slots:
void SetParams(const AudioParams& params);
void OutputDeviceSet(const QString& filename, qint64 offset, int playback_speed);
void OutputDeviceSet(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
void Stop();
@@ -66,7 +65,7 @@ private:
AudioParams params_;
QFile file_;
QIODevice* file_;
qint64 last_time_;
int playback_speed_;
+110 -117
View File
@@ -60,19 +60,58 @@ void CurveView::Clear()
lines_.clear();
}
void CurveView::SetTrackCount(int count)
void CurveView::ConnectInput(NodeInput *input)
{
track_count_ = count;
if (connected_inputs_.contains(input)) {
// Input wasn't connected, do nothing
return;
}
track_visible_.resize(track_count_);
track_visible_.fill(true);
// Add keyframes from this input
foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
this->AddKeyframe(key);
}
if (!keyframe_colors_.contains(&track)) {
// Generate a random color for this input
keyframe_colors_.insert(&track, QColor::fromHsv(std::rand()%360, std::rand()%255, 255));
}
}
// Append to the list
connected_inputs_.append(input);
// Connect add/remove signals
connect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe);
connect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe);
}
void CurveView::SetTrackVisible(int track, bool visible)
void CurveView::DisconnectNode(Node *node)
{
track_visible_[track] = visible;
QList<NodeInput*> inputs = node->GetInputsIncludingArrays();
SetKeyframeTrackVisible(track, visible);
foreach (NodeInput* i, inputs) {
DisconnectInput(i);
}
}
void CurveView::DisconnectInput(NodeInput *input)
{
if (!connected_inputs_.contains(input)) {
// Input wasn't connected, do nothing
return;
}
// Remove keyframes belonging to this input
RemoveKeyframesOfInput(input);
// Remove from the list
connected_inputs_.removeOne(input);
// Disconnect add/remove signals
disconnect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe);
disconnect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe);
}
void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
@@ -126,87 +165,85 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
painter->drawLines(lines);
// Draw keyframe lines
foreach (NodeInput* input, connected_inputs_) {
if (input->is_keyframing()) {
foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) {
if (!track.isEmpty()) {
painter->setPen(QPen(keyframe_colors_.value(&track), qMax(1, fontMetrics().height() / 4)));
for (int j=0;j<track_count_;j++) {
if (!track_visible_.at(j)) {
continue;
}
QVector<QLineF> keyframe_lines;
painter->setPen(QPen(GetKeyframeColor(j), qMax(1, fontMetrics().height() / 4)));
QList<NodeKeyframe*> keys = GetKeyframesSortedByTime(j);
// Draw straight line leading to first keyframe
QPointF first_key_pos = item_map().value(track.first().get())->pos();
keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos));
if (!keys.isEmpty()) {
QVector<QLineF> keyframe_lines;
// Draw lines between each keyframe
for (int i=1;i<track.size();i++) {
NodeKeyframePtr before = track.at(i-1);
NodeKeyframePtr after = track.at(i);
// Draw straight line leading to first keyframe
QPointF first_key_pos = item_map().value(keys.first())->pos();
keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos));
KeyframeViewItem* before_item = item_map().value(before.get());
KeyframeViewItem* after_item = item_map().value(after.get());
// Draw lines between each keyframe
for (int i=1;i<keys.size();i++) {
NodeKeyframe* before = keys.at(i-1);
NodeKeyframe* after = keys.at(i);
if (before->type() == NodeKeyframe::kHold) {
// Draw a hold keyframe (basically a right angle)
keyframe_lines.append(QLineF(before_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
before_item->pos().y()));
keyframe_lines.append(QLineF(after_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
after_item->pos().y()));
} else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
// Draw a cubic bezier
KeyframeViewItem* before_item = item_map().value(before);
KeyframeViewItem* after_item = item_map().value(after);
// Cubic beziers have two control points, so we can just use both
QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out());
QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in());
if (before->type() == NodeKeyframe::kHold) {
// Draw a hold keyframe (basically a right angle)
keyframe_lines.append(QLineF(before_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
before_item->pos().y()));
keyframe_lines.append(QLineF(after_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
after_item->pos().y()));
} else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
// Draw a cubic bezier
QPainterPath path;
path.moveTo(before_item->pos());
path.cubicTo(before_control_point, after_control_point, after_item->pos());
painter->drawPath(path);
// Cubic beziers have two control points, so we can just use both
QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out());
QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in());
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Draw a quadratic bezier
QPainterPath path;
path.moveTo(before_item->pos());
path.cubicTo(before_control_point, after_control_point, after_item->pos());
painter->drawPath(path);
// Quadratic beziers have a single control point, we just have to determine which it is
QPointF key_anchor;
QPointF control_point;
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Draw a quadratic bezier
if (before->type() == NodeKeyframe::kBezier) {
key_anchor = before_item->pos();
control_point = before->bezier_control_out();
} else {
key_anchor = after_item->pos();
control_point = after->bezier_control_in();
}
// Quadratic beziers have a single control point, we just have to determine which it is
QPointF key_anchor;
QPointF control_point;
// Scale control point
control_point = key_anchor + ScalePoint(control_point);
if (before->type() == NodeKeyframe::kBezier) {
key_anchor = before_item->pos();
control_point = before->bezier_control_out();
} else {
key_anchor = after_item->pos();
control_point = after->bezier_control_in();
// Create the path from both keyframes
QPainterPath path;
path.moveTo(before_item->pos());
path.quadTo(control_point, after_item->pos());
painter->drawPath(path);
} else {
// Linear to linear
keyframe_lines.append(QLineF(before_item->pos(), after_item->pos()));
}
}
// Scale control point
control_point = key_anchor + ScalePoint(control_point);
// Draw straight line leading from end keyframe
QPointF last_key_pos = item_map().value(track.last().get())->pos();
keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y())));
// Create the path from both keyframes
QPainterPath path;
path.moveTo(before_item->pos());
path.quadTo(control_point, after_item->pos());
painter->drawPath(path);
} else {
// Linear to linear
keyframe_lines.append(QLineF(before_item->pos(), after_item->pos()));
painter->drawLines(keyframe_lines);
}
}
// Draw straight line leading from end keyframe
QPointF last_key_pos = item_map().value(keys.last())->pos();
keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y())));
painter->drawLines(keyframe_lines);
}
}
@@ -269,37 +306,6 @@ void CurveView::ContextMenuEvent(Menu &m)
//QAction* reset_zoom_action = m.addAction(tr("Reset Zoom"));
}
QList<NodeKeyframe *> CurveView::GetKeyframesSortedByTime(int track)
{
QList<NodeKeyframe *> sorted;
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator iterator;
for (iterator=item_map().begin();iterator!=item_map().end();iterator++) {
NodeKeyframe* key = iterator.key();
if (key->track() != track) {
continue;
}
bool inserted = false;
for (int i=0;i<sorted.size();i++) {
if (sorted.at(i)->time() > key->time()) {
sorted.insert(i, key);
inserted = true;
break;
}
}
if (!inserted) {
sorted.append(key);
}
}
return sorted;
}
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
{
return GetItemYFromKeyframeValue(key->value().toDouble());
@@ -334,17 +340,6 @@ void CurveView::CreateBezierControlPoints(KeyframeViewItem* item)
connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);
}
QColor CurveView::GetKeyframeColor(int track) const
{
if (track_count_) {
QColor c;
c.setHsvF(static_cast<double>(track) / static_cast<double>(track_count_), 0.5, 1.0);
return c;
}
return palette().text().color();
}
void CurveView::KeyframeValueChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
@@ -396,15 +391,13 @@ void CurveView::ZoomToFit()
return;
}
QMap<NodeKeyframe *, KeyframeViewItem *>::const_iterator i;
rational min_time = RATIONAL_MAX;
rational max_time = RATIONAL_MIN;
double min_val = DBL_MAX;
double max_val = DBL_MIN;
for (i=item_map().constBegin(); i!=item_map().constEnd(); i++) {
for (auto i=item_map().constBegin(); i!=item_map().constEnd(); i++) {
rational transformed_time = GetAdjustedTime(i.key()->parent()->parentNode(),
GetTimeTarget(),
i.key()->time(),
@@ -432,7 +425,7 @@ void CurveView::AddKeyframe(NodeKeyframePtr key)
{
KeyframeViewItem* item = AddKeyframeInternal(key);
SetItemYFromKeyframeValue(key.get(), item);
item->SetOverrideBrush(GetKeyframeColor(key->track()));
item->SetOverrideBrush(keyframe_colors_.value(&key->parent()->keyframe_tracks().at(key->track())));
connect(key.get(), &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
connect(key.get(), &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
+6 -8
View File
@@ -38,9 +38,11 @@ public:
virtual void Clear() override;
void SetTrackCount(int count);
void ConnectInput(NodeInput* input);
void SetTrackVisible(int track, bool visible);
void DisconnectNode(Node* node);
void DisconnectInput(NodeInput* input);
public slots:
void AddKeyframe(NodeKeyframePtr key);
@@ -61,8 +63,6 @@ protected:
virtual void ContextMenuEvent(Menu &m) override;
private:
QList<NodeKeyframe*> GetKeyframesSortedByTime(int track);
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
qreal GetItemYFromKeyframeValue(double value);
@@ -74,7 +74,7 @@ private:
void CreateBezierControlPoints(KeyframeViewItem *item);
QColor GetKeyframeColor(int track) const;
QMap<const NodeInput::KeyframeTrack*, QColor> keyframe_colors_;
int text_padding_;
@@ -84,9 +84,7 @@ private:
QList<BezierControlPointItem*> bezier_control_points_;
QVector<bool> track_visible_;
int track_count_;
QList<NodeInput*> connected_inputs_;
private slots:
void KeyframeValueChanged();
+70 -97
View File
@@ -23,6 +23,7 @@
#include <QEvent>
#include <QLabel>
#include <QScrollBar>
#include <QSplitter>
#include <QVBoxLayout>
#include "core.h"
@@ -34,12 +35,23 @@
OLIVE_NAMESPACE_ENTER
CurveWidget::CurveWidget(QWidget *parent) :
TimeBasedWidget(parent),
input_(nullptr),
bridge_(nullptr)
TimeBasedWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
QHBoxLayout* outer_layout = new QHBoxLayout(this);
QSplitter* splitter = new QSplitter();
outer_layout->addWidget(splitter);
tree_view_ = new NodeTreeView();
tree_view_->SetOnlyShowKeyframable(true);
connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged);
connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged);
splitter->addWidget(tree_view_);
QWidget* workarea = new QWidget();
QVBoxLayout* layout = new QVBoxLayout(workarea);
layout->setMargin(0);
splitter->addWidget(workarea);
QHBoxLayout* top_controls = new QHBoxLayout();
@@ -92,12 +104,8 @@ CurveWidget::CurveWidget(QWidget *parent) :
view_->setHorizontalScrollBar(scrollbar());
connect(view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
widget_bridge_layout_ = new QHBoxLayout();
widget_bridge_layout_->addStretch();
input_label_ = new QLabel();
widget_bridge_layout_->addWidget(input_label_);
widget_bridge_layout_->addStretch();
layout->addLayout(widget_bridge_layout_);
// Disable collapsing the main curve view (but allow collapsing the tree)
splitter->setCollapsible(1, false);
SetScale(120.0);
}
@@ -108,71 +116,6 @@ CurveWidget::~CurveWidget()
view_->Clear();
}
NodeInput *CurveWidget::GetInput() const
{
return input_;
}
void CurveWidget::SetInput(NodeInput *input)
{
if (bridge_) {
foreach (QWidget* bridge_widget, bridge_->widgets()) {
bridge_widget->deleteLater();
}
bridge_->deleteLater();
bridge_ = nullptr;
}
foreach (QCheckBox* box, checkboxes_) {
box->deleteLater();
}
checkboxes_.clear();
if (input_) {
disconnect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe);
disconnect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe);
}
view_->Clear();
input_ = input;
key_control_->SetInput(input_);
if (input_) {
view_->SetTrackCount(input_->get_number_of_keyframe_tracks());
bridge_ = new NodeParamViewWidgetBridge(input_, this);
bridge_->SetTimeTarget(GetTimeTarget());
for (int i=0;i<bridge_->widgets().size();i++) {
// Insert between two stretches to center the widget
QCheckBox* checkbox = new QCheckBox();
checkbox->setChecked(true);
widget_bridge_layout_->insertWidget(2 + i*2, checkbox);
checkboxes_.append(checkbox);
connect(checkbox, &QCheckBox::clicked, this, [this](bool e){
view_->SetTrackVisible(checkboxes_.indexOf(static_cast<QCheckBox*>(sender())), e);
});
widget_bridge_layout_->insertWidget(2 + i*2 + 1, bridge_->widgets().at(i));
}
connect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe);
connect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe);
foreach (const NodeInput::KeyframeTrack& track, input_->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
view_->AddKeyframe(key);
}
}
}
UpdateInputLabel();
QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection);
}
const double &CurveWidget::GetVerticalScale()
{
return view_->GetYScale();
@@ -188,12 +131,26 @@ void CurveWidget::DeleteSelected()
view_->DeleteSelected();
}
void CurveWidget::changeEvent(QEvent *e)
void CurveWidget::SetNodes(const QList<Node *> &nodes)
{
if (e->type() == QEvent::LanguageChange) {
UpdateInputLabel();
tree_view_->SetNodes(nodes);
// Detect removed nodes
foreach (Node* n, nodes_) {
if (!nodes.contains(n)) {
view_->DisconnectNode(n);
}
}
QWidget::changeEvent(e);
// Detect added nodes
foreach (Node* n, nodes) {
if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) {
ConnectNode(n);
}
}
// Save new node list
nodes_ = nodes;
}
void CurveWidget::TimeChangedEvent(const int64_t &timestamp)
@@ -223,10 +180,6 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
key_control_->SetTimeTarget(target);
view_->SetTimeTarget(target);
if (bridge_) {
bridge_->SetTimeTarget(target);
}
}
void CurveWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -234,15 +187,6 @@ void CurveWidget::ConnectedNodeChanged(ViewerOutput *n)
SetTimeTarget(n);
}
void CurveWidget::UpdateInputLabel()
{
if (input_) {
input_label_->setText(QStringLiteral("%1 :: %2:").arg(input_->parentNode()->Name(), input_->name()));
} else {
input_label_->clear();
}
}
void CurveWidget::SetKeyframeButtonEnabled(bool enable)
{
linear_button_->setEnabled(enable);
@@ -266,15 +210,26 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type)
void CurveWidget::UpdateBridgeTime(const int64_t &timestamp)
{
if (!input_) {
return;
}
rational time = Timecode::timestamp_to_time(timestamp, view_->timebase());
bridge_->SetTime(time);
key_control_->SetTime(time);
}
void CurveWidget::ConnectNode(Node *n)
{
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
if (tree_view_->IsInputEnabled(i)) {
view_->ConnectInput(i);
}
}
}
void CurveWidget::DisconnectNode(Node *n)
{
view_->DisconnectNode(n);
}
void CurveWidget::SelectionChanged()
{
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
@@ -349,4 +304,22 @@ void CurveWidget::KeyControlRequestedTimeChanged(const rational &time)
SetTimeAndSignal(Timecode::time_to_timestamp(time, view_->timebase()));
}
void CurveWidget::NodeEnabledChanged(Node* n, bool e)
{
if (e) {
ConnectNode(n);
} else {
DisconnectNode(n);
}
}
void CurveWidget::InputEnabledChanged(NodeInput *i, bool e)
{
if (e) {
view_->ConnectInput(i);
} else {
view_->DisconnectInput(i);
}
}
OLIVE_NAMESPACE_EXIT
+16 -15
View File
@@ -30,6 +30,7 @@
#include "node/input.h"
#include "widget/nodeparamview/nodeparamviewkeyframecontrol.h"
#include "widget/nodeparamview/nodeparamviewwidgetbridge.h"
#include "widget/nodetreeview/nodetreeview.h"
#include "widget/timebased/timebased.h"
OLIVE_NAMESPACE_ENTER
@@ -42,17 +43,15 @@ public:
virtual ~CurveWidget() override;
NodeInput* GetInput() const;
void SetInput(NodeInput* input);
const double& GetVerticalScale();
void SetVerticalScale(const double& vscale);
void DeleteSelected();
protected:
virtual void changeEvent(QEvent *) override;
public slots:
void SetNodes(const QList<Node*>& nodes);
protected:
virtual void TimeChangedEvent(const int64_t &) override;
virtual void TimebaseChangedEvent(const rational &) override;
virtual void ScaleChangedEvent(const double &) override;
@@ -62,8 +61,6 @@ protected:
virtual void ConnectedNodeChanged(ViewerOutput* n) override;
private:
void UpdateInputLabel();
void SetKeyframeButtonEnabled(bool enable);
void SetKeyframeButtonChecked(bool checked);
@@ -72,6 +69,12 @@ private:
void UpdateBridgeTime(const int64_t& timestamp);
void ConnectNode(Node* n);
void DisconnectNode(Node* n);
NodeTreeView* tree_view_;
QPushButton* linear_button_;
QPushButton* bezier_button_;
@@ -80,18 +83,12 @@ private:
CurveView* view_;
NodeInput* input_;
QLabel* input_label_;
QHBoxLayout* widget_bridge_layout_;
NodeParamViewWidgetBridge* bridge_;
NodeParamViewKeyframeControl* key_control_;
QList<QCheckBox*> checkboxes_;
QList<Node*> nodes_;
private slots:
void SelectionChanged();
@@ -99,6 +96,10 @@ private slots:
void KeyControlRequestedTimeChanged(const rational& time);
void NodeEnabledChanged(Node* n, bool e);
void InputEnabledChanged(NodeInput* i, bool e);
};
OLIVE_NAMESPACE_EXIT
+9 -29
View File
@@ -79,10 +79,15 @@ void KeyframeViewBase::RemoveKeyframesOfNode(Node *n)
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
RemoveKeyframe(key);
}
RemoveKeyframesOfInput(i);
}
}
void KeyframeViewBase::RemoveKeyframesOfInput(NodeInput *i)
{
foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
RemoveKeyframe(key);
}
}
}
@@ -104,10 +109,6 @@ KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key)
item->SetScale(GetScale());
item_map_.insert(key.get(), item);
scene()->addItem(item);
if (hidden_tracks_.contains(key->track())) {
item->setVisible(false);
}
}
return item;
@@ -301,27 +302,6 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target)
}
}
void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible)
{
if (!visible == hidden_tracks_.contains(track)) {
return;
}
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
if (i.key()->track() == track) {
i.value()->setVisible(visible);
}
}
if (visible) {
hidden_tracks_.removeOne(track);
} else {
hidden_tracks_.append(track);
}
}
void KeyframeViewBase::ContextMenuEvent(Menu& m)
{
Q_UNUSED(m)
+2 -4
View File
@@ -42,6 +42,8 @@ public:
void RemoveKeyframesOfNode(Node* n);
void RemoveKeyframesOfInput(NodeInput* i);
public slots:
void RemoveKeyframe(NodeKeyframePtr key);
@@ -60,8 +62,6 @@ protected:
virtual void TimeTargetChangedEvent(Node*) override;
void SetKeyframeTrackVisible(int track, bool visible);
virtual void ContextMenuEvent(Menu &m);
private:
@@ -96,8 +96,6 @@ private:
bool currently_autoselecting_;
QList<int> hidden_tracks_;
private slots:
void ShowContextMenu();
+6 -8
View File
@@ -100,16 +100,14 @@ void Menu::ConformItem(QAction *a, const QString &id, const QString &key)
}
}
void Menu::SetBooleanAction(QAction *a, bool* boolean)
{
// FIXME: Connect to some boolean function
a->setCheckable(true);
a->setChecked(*boolean);
a->setProperty("boolptr", reinterpret_cast<quintptr>(boolean));
}
void Menu::Init()
{
// HACK: Disables embossing on disabled text for a slightly nicer UI
QPalette p = palette();
p.setColor(QPalette::Disabled, QPalette::Light, QColor(0, 0, 0, 0));
setPalette(p);
// If a native palette is available, it should override the one above
StyleManager::UseOSNativeStyling(this);
}
-2
View File
@@ -226,8 +226,6 @@ public:
const QString& id,
const QString& key = QString());
static void SetBooleanAction(QAction* a, bool *boolean);
private:
void Init();
+67
View File
@@ -58,6 +58,34 @@ MenuShared::MenuShared()
clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", this, &MenuShared::EnableDisableTriggered, "Shift+E");
clip_nest_item_ = Menu::CreateItem(this, "nest", this, &MenuShared::NestTriggered);
// TimeRuler menu shared items
frame_view_mode_group_ = new QActionGroup(this);
view_timecode_view_dropframe_item_ = Menu::CreateItem(this, "modedropframe", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_dropframe_item_->setData(Timecode::kTimecodeDropFrame);
view_timecode_view_dropframe_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_dropframe_item_);
view_timecode_view_nondropframe_item_ = Menu::CreateItem(this, "modenondropframe", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_nondropframe_item_->setData(Timecode::kTimecodeNonDropFrame);
view_timecode_view_nondropframe_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_nondropframe_item_);
view_timecode_view_seconds_item_ = Menu::CreateItem(this, "modeseconds", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_seconds_item_->setData(Timecode::kTimecodeSeconds);
view_timecode_view_seconds_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_seconds_item_);
view_timecode_view_frames_item_ = Menu::CreateItem(this, "modeframes", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_frames_item_->setData(Timecode::kFrames);
view_timecode_view_frames_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_frames_item_);
view_timecode_view_milliseconds_item_ = Menu::CreateItem(this, "milliseconds", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_milliseconds_item_->setData(Timecode::kMilliseconds);
view_timecode_view_milliseconds_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_milliseconds_item_);
Retranslate();
}
@@ -117,6 +145,26 @@ void MenuShared::AddItemsForClipEditMenu(Menu *m)
m->addAction(clip_nest_item_);
}
void MenuShared::AddItemsForTimeRulerMenu(Menu *m)
{
m->addAction(view_timecode_view_dropframe_item_);
m->addAction(view_timecode_view_nondropframe_item_);
m->addAction(view_timecode_view_seconds_item_);
m->addAction(view_timecode_view_frames_item_);
m->addAction(view_timecode_view_milliseconds_item_);
}
void MenuShared::AboutToShowTimeRulerActions()
{
QList<QAction*> timecode_display_actions = frame_view_mode_group_->actions();
foreach (QAction* a, timecode_display_actions) {
if (a->data() == Core::instance()->GetTimecodeDisplay()) {
a->setChecked(true);
break;
}
}
}
MenuShared *MenuShared::instance()
{
return instance_;
@@ -211,6 +259,18 @@ void MenuShared::DefaultTransitionTriggered()
qDebug() << "FIXME: Stub";
}
void MenuShared::TimecodeDisplayTriggered()
{
// Assume the sender is a QAction
QAction* action = static_cast<QAction*>(sender());
// Assume its data() is a member of Timecode::Display
Timecode::Display display = static_cast<Timecode::Display>(action->data().toInt());
// Set the current display mode
Core::instance()->SetTimecodeDisplay(display);
}
void MenuShared::Retranslate()
{
// "New" menu shared items
@@ -240,6 +300,13 @@ void MenuShared::Retranslate()
clip_link_unlink_item_->setText(tr("Link/Unlink"));
clip_enable_disable_item_->setText(tr("Enable/Disable"));
clip_nest_item_->setText(tr("Nest"));
// TimeRuler menu shared items
view_timecode_view_frames_item_->setText(tr("Frames"));
view_timecode_view_dropframe_item_->setText(tr("Drop Frame"));
view_timecode_view_nondropframe_item_->setText(tr("Non-Drop Frame"));
view_timecode_view_milliseconds_item_->setText(tr("Milliseconds"));
view_timecode_view_seconds_item_->setText(tr("Seconds"));
}
OLIVE_NAMESPACE_EXIT
+19
View File
@@ -42,6 +42,9 @@ public:
void AddItemsForEditMenu(Menu* m, bool for_clips);
void AddItemsForInOutMenu(Menu* m);
void AddItemsForClipEditMenu(Menu* m);
void AddItemsForTimeRulerMenu(Menu* m);
void AboutToShowTimeRulerActions();
static MenuShared* instance();
@@ -74,6 +77,14 @@ private:
QAction* clip_enable_disable_item_;
QAction* clip_nest_item_;
// TimeRuler menu shared items
QActionGroup* frame_view_mode_group_;
QAction* view_timecode_view_dropframe_item_;
QAction* view_timecode_view_nondropframe_item_;
QAction* view_timecode_view_seconds_item_;
QAction* view_timecode_view_frames_item_;
QAction* view_timecode_view_milliseconds_item_;
static MenuShared* instance_;
private slots:
@@ -111,6 +122,14 @@ private slots:
void DefaultTransitionTriggered();
/**
* @brief A slot for the timecode display menu items
*
* Assumes a QAction* sender() and its data() is a member of enum Timecode::Display. Uses the data() to signal a
* timecode change throughout the rest of the application.
*/
void TimecodeDisplayTriggered();
};
OLIVE_NAMESPACE_EXIT
+184 -35
View File
@@ -20,6 +20,7 @@
#include "nodeparamview.h"
#include <QApplication>
#include <QScrollArea>
#include <QScrollBar>
#include <QSplitter>
@@ -31,7 +32,8 @@ OLIVE_NAMESPACE_ENTER
NodeParamView::NodeParamView(QWidget *parent) :
TimeBasedWidget(true, false, parent),
last_scroll_val_(0)
last_scroll_val_(0),
focused_node_(nullptr)
{
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -49,18 +51,26 @@ NodeParamView::NodeParamView(QWidget *parent) :
splitter->addWidget(scroll_area);
// Param widget
param_widget_area_ = new QWidget();
scroll_area->setWidget(param_widget_area_);
param_widget_container_ = new NodeParamViewParamContainer();
connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar);
scroll_area->setWidget(param_widget_container_);
// Set up scroll area layout
param_layout_ = new QVBoxLayout(param_widget_area_);
param_layout_->setSpacing(0);
param_widget_area_ = new QMainWindow();
// KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too
param_layout_->setContentsMargins(0, ruler()->height(), 0, 0);
// Disable dock widgets from tabbing and disable glitchy animations
param_widget_area_->setDockOptions(static_cast<QMainWindow::DockOption>(0));
// Add a stretch to allow empty space at the bottom of the layout
param_layout_->addStretch();
// HACK: Hide the main window separators (unfortunately the cursors still appear)
param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}"));
QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_);
QMargins param_widget_margin = param_widget_container_layout->contentsMargins();
param_widget_margin.setTop(ruler()->height());
param_widget_container_layout->setContentsMargins(param_widget_margin);
param_widget_container_layout->setSpacing(0);
param_widget_container_layout->addWidget(param_widget_area_);
param_widget_container_layout->addStretch(INT_MAX);
// Set up keyframe view
QWidget* keyframe_area = new QWidget();
@@ -100,9 +110,6 @@ NodeParamView::NodeParamView(QWidget *parent) :
layout->addWidget(vertical_scrollbar_);
// Connect scrollbars together
connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange);
connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll);
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
@@ -120,46 +127,89 @@ NodeParamView::NodeParamView(QWidget *parent) :
SetScale(120);
SetMaximumScale(TimelineViewBase::kMaximumScale);
// Pickup on widget focus changes
connect(qApp,
&QApplication::focusChanged,
this,
&NodeParamView::FocusChanged);
}
void NodeParamView::SelectNodes(const QList<Node *> &nodes)
{
active_nodes_.append(nodes);
bool changes_made = false;
foreach (Node* n, nodes) {
NodeParamViewItem* item = new NodeParamViewItem(n);
if (!pinned_nodes_.contains(n)) {
NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_);
// Insert the widget before the stretch
param_layout_->insertWidget(param_layout_->count() - 1, item);
item->setAllowedAreas(Qt::LeftDockWidgetArea);
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
item->SetExpanded(node_expanded_state_.value(n, true));
connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged);
connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged);
connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder);
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
// Set time target
item->SetTimeTarget(GetTimeTarget());
// Set time target
item->SetTimeTarget(GetTimeTarget());
items_.insert(n, item);
items_.insert(n, item);
param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item);
changes_made = true;
if (!focused_node_ && n->HasGizmos()) {
// We'll focus this node now
item->SetHighlighted(true);
focused_node_ = n;
emit FocusedNodeChanged(focused_node_);
}
}
}
UpdateItemTime(GetTimestamp());
if (changes_made) {
UpdateItemTime(GetTimestamp());
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
// Re-arrange keyframes
QueueKeyframePositionUpdate();
SignalNodeOrder();
}
}
void NodeParamView::DeselectNodes(const QList<Node *> &nodes)
{
// Remove item from map and delete the widget
foreach (Node* n, nodes) {
// Remove all keyframes from this node
keyframe_view_->RemoveKeyframesOfNode(n);
bool changes_made = false;
delete items_.take(n);
foreach (Node* n, nodes) {
if (!pinned_nodes_.contains(n)) {
// Store expanded state
node_expanded_state_.insert(n, items_.value(n)->IsExpanded());
// Remove all keyframes from this node
RemoveNode(n);
changes_made = true;
}
active_nodes_.removeOne(n);
}
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
if (changes_made) {
// Re-arrange keyframes
QueueKeyframePositionUpdate();
SignalNodeOrder();
}
}
void NodeParamView::resizeEvent(QResizeEvent *event)
@@ -167,6 +217,8 @@ void NodeParamView::resizeEvent(QResizeEvent *event)
QWidget::resizeEvent(event);
vertical_scrollbar_->setPageStep(vertical_scrollbar_->height());
UpdateGlobalScrollBar();
}
void NodeParamView::ScaleChangedEvent(const double &scale)
@@ -223,14 +275,63 @@ void NodeParamView::UpdateItemTime(const int64_t &timestamp)
}
}
void NodeParamView::QueueKeyframePositionUpdate()
{
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
}
void NodeParamView::SignalNodeOrder()
{
// Sort by item Y (apparently there's no way in Qt to get the order of dock widgets)
QList<Node*> nodes;
QList<int> item_ys;
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
int item_y = it.value()->pos().y();
bool inserted = false;
for (int i=0; i<item_ys.size(); i++) {
if (item_ys.at(i) > item_y) {
item_ys.insert(i, item_y);
nodes.insert(i, it.key());
inserted = true;
break;
}
}
if (!inserted) {
item_ys.append(item_y);
nodes.append(it.key());
}
}
emit NodeOrderChanged(nodes);
}
void NodeParamView::RemoveNode(Node *n)
{
keyframe_view_->RemoveKeyframesOfNode(n);
delete items_.take(n);
if (focused_node_ == n) {
focused_node_ = nullptr;
emit FocusedNodeChanged(nullptr);
}
}
void NodeParamView::ItemRequestedTimeChanged(const rational &time)
{
SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase()));
}
void NodeParamView::ForceKeyframeViewToScroll()
void NodeParamView::UpdateGlobalScrollBar()
{
keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height());
int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height();
keyframe_view_->SetMaxScroll(height_offscreen);
vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height());
}
void NodeParamView::PlaceKeyframesOnView()
@@ -240,4 +341,52 @@ void NodeParamView::PlaceKeyframesOnView()
}
}
void NodeParamView::PinNode(bool pin)
{
NodeParamViewItem* item = static_cast<NodeParamViewItem*>(sender());
Node* node = item->GetNode();
if (pin) {
pinned_nodes_.append(node);
} else {
pinned_nodes_.removeOne(node);
if (!active_nodes_.contains(node)) {
RemoveNode(node);
}
}
}
void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
{
Q_UNUSED(old)
QObject* parent = now;
NodeParamViewItem* item;
while (parent) {
item = dynamic_cast<NodeParamViewItem*>(parent);
if (item) {
// Found it!
if (item->GetNode() != focused_node_) {
if (focused_node_) {
// De-focus current node
items_.value(focused_node_)->SetHighlighted(false);
}
focused_node_ = item->GetNode();
item->SetHighlighted(true);
emit FocusedNodeChanged(focused_node_);
}
break;
}
parent = parent->parent();
}
}
OLIVE_NAMESPACE_EXIT
+50 -3
View File
@@ -21,6 +21,7 @@
#ifndef NODEPARAMVIEW_H
#define NODEPARAMVIEW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QWidget>
@@ -31,6 +32,28 @@
OLIVE_NAMESPACE_ENTER
class NodeParamViewParamContainer : public QWidget
{
Q_OBJECT
public:
NodeParamViewParamContainer(QWidget* parent = nullptr) :
QWidget(parent)
{
}
protected:
virtual void resizeEvent(QResizeEvent *event) override
{
QWidget::resizeEvent(event);
emit Resized(event->size().height());
}
signals:
void Resized(int new_height);
};
class NodeParamView : public TimeBasedWidget
{
Q_OBJECT
@@ -54,6 +77,10 @@ signals:
void RequestSelectNode(const QList<Node*>& target);
void NodeOrderChanged(const QList<Node*>& nodes);
void FocusedNodeChanged(Node* n);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -66,7 +93,11 @@ protected:
private:
void UpdateItemTime(const int64_t &timestamp);
QVBoxLayout* param_layout_;
void QueueKeyframePositionUpdate();
void SignalNodeOrder();
void RemoveNode(Node* n);
KeyframeView* keyframe_view_;
@@ -76,15 +107,31 @@ private:
int last_scroll_val_;
QWidget* param_widget_area_;
NodeParamViewParamContainer* param_widget_container_;
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
// docking windows
QMainWindow* param_widget_area_;
QList<Node*> pinned_nodes_;
QList<Node*> active_nodes_;
QMap<Node*, bool> node_expanded_state_;
Node* focused_node_;
private slots:
void ItemRequestedTimeChanged(const rational& time);
void ForceKeyframeViewToScroll();
void UpdateGlobalScrollBar();
void PlaceKeyframesOnView();
void PinNode(bool pin);
void FocusChanged(QWidget *old, QWidget *now);
};
OLIVE_NAMESPACE_EXIT
+94 -27
View File
@@ -32,27 +32,15 @@
OLIVE_NAMESPACE_ENTER
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
QWidget(parent),
node_(node)
QDockWidget(parent),
node_(node),
highlighted_(false)
{
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setSpacing(0);
main_layout->setMargin(0);
// Create title bar widget
title_bar_ = new NodeParamViewItemTitleBar(this);
title_bar_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
QHBoxLayout* title_bar_layout = new QHBoxLayout(title_bar_);
title_bar_collapse_btn_ = new CollapseButton();
title_bar_layout->addWidget(title_bar_collapse_btn_);
title_bar_lbl_ = new QLabel(title_bar_);
title_bar_layout->addWidget(title_bar_lbl_);
// Add title bar to widget
main_layout->addWidget(title_bar_);
this->setTitleBarWidget(title_bar_);
// Create and add contents widget
QVector<NodeInput*> inputs;
@@ -70,11 +58,26 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
connect(body_, &NodeParamViewItemBody::KeyframeAdded, this, &NodeParamViewItem::KeyframeAdded);
connect(body_, &NodeParamViewItemBody::KeyframeRemoved, this, &NodeParamViewItem::KeyframeRemoved);
connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible);
main_layout->addWidget(body_);
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded);
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled);
QWidget* body_container = new QWidget();
body_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QHBoxLayout* body_container_layout = new QHBoxLayout(body_container);
body_container_layout->setSpacing(0);
body_container_layout->setMargin(0);
body_container_layout->addWidget(body_);
this->setWidget(body_container);
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
setFocusPolicy(Qt::ClickFocus);
Retranslate();
}
@@ -109,34 +112,98 @@ void NodeParamViewItem::changeEvent(QEvent *e)
QWidget::changeEvent(e);
}
void NodeParamViewItem::paintEvent(QPaintEvent *event)
{
QDockWidget::paintEvent(event);
// Draw border if focused
if (highlighted_) {
QPainter p(this);
p.setBrush(Qt::NoBrush);
p.setPen(palette().highlight().color());
p.drawRect(rect().adjusted(0, 0, -1, -1));
}
}
void NodeParamViewItem::Retranslate()
{
node_->Retranslate();
if (node_->GetLabel().isEmpty()) {
title_bar_lbl_->setText(node_->Name());
title_bar_->SetText(node_->Name());
} else {
title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
}
body_->Retranslate();
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
QWidget(parent)
void NodeParamViewItem::SetExpanded(bool e)
{
body_->setVisible(e);
title_bar_->SetExpanded(e);
}
bool NodeParamViewItem::IsExpanded() const
{
return body_->isVisible();
}
void NodeParamViewItem::ToggleExpanded()
{
SetExpanded(!IsExpanded());
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
QWidget(parent),
draw_border_(true)
{
QHBoxLayout* layout = new QHBoxLayout(this);
collapse_btn_ = new CollapseButton();
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
layout->addWidget(collapse_btn_);
lbl_ = new QLabel();
layout->addWidget(lbl_);
// Place next buttons on the far side
layout->addStretch();
QPushButton* pin_btn = new QPushButton(QStringLiteral("P"));
pin_btn->setCheckable(true);
pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height());
layout->addWidget(pin_btn);
connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
}
void NodeParamViewItemTitleBar::SetExpanded(bool e)
{
draw_border_ = e;
collapse_btn_->setChecked(e);
update();
}
void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter p(this);
if (draw_border_) {
QPainter p(this);
// Draw bottom border using text color
int bottom = height() - 1;
p.setPen(palette().text().color());
p.drawLine(0, bottom, width(), bottom);
// Draw bottom border using text color
int bottom = height() - 1;
p.setPen(palette().text().color());
p.drawLine(0, bottom, width(), bottom);
}
}
void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
QWidget::mouseDoubleClickEvent(event);
collapse_btn_->click();
}
NodeParamViewItemBody::NodeParamViewItemBody(const QVector<NodeInput *> &inputs, QWidget *parent) :
+46 -6
View File
@@ -21,6 +21,7 @@
#ifndef NODEPARAMVIEWITEM_H
#define NODEPARAMVIEWITEM_H
#include <QDockWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
@@ -36,12 +37,36 @@
OLIVE_NAMESPACE_ENTER
class NodeParamViewItemTitleBar : public QWidget {
class NodeParamViewItemTitleBar : public QWidget
{
Q_OBJECT
public:
NodeParamViewItemTitleBar(QWidget* parent = nullptr);
void SetExpanded(bool e);
void SetText(const QString& s)
{
lbl_->setText(s);
}
signals:
void ExpandedStateChanged(bool e);
void PinToggled(bool e);
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
bool draw_border_;
QLabel* lbl_;
CollapseButton* collapse_btn_;
};
class NodeParamViewItemBody : public QWidget {
@@ -99,7 +124,7 @@ private slots:
};
class NodeParamViewItem : public QWidget
class NodeParamViewItem : public QDockWidget
{
Q_OBJECT
public:
@@ -111,9 +136,22 @@ public:
Node* GetNode() const;
bool IsExpanded() const;
void SetHighlighted(bool e)
{
highlighted_ = e;
update();
}
public slots:
void SignalAllKeyframes();
void SetExpanded(bool e);
void ToggleExpanded();
signals:
void KeyframeAdded(NodeKeyframePtr key, int y);
@@ -125,22 +163,24 @@ signals:
void RequestSelectNode(const QList<Node*>& node);
void PinToggled(bool e);
protected:
virtual void changeEvent(QEvent *e) override;
virtual void paintEvent(QPaintEvent *event) override;
private:
NodeParamViewItemTitleBar* title_bar_;
QLabel* title_bar_lbl_;
CollapseButton* title_bar_collapse_btn_;
NodeParamViewItemBody* body_;
Node* node_;
rational time_;
bool highlighted_;
private slots:
void Retranslate();
+22
View File
@@ -0,0 +1,22 @@
# 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 <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodetreeview/nodetreeview.h
widget/nodetreeview/nodetreeview.cpp
PARENT_SCOPE
)
+111
View File
@@ -0,0 +1,111 @@
#include "nodetreeview.h"
OLIVE_NAMESPACE_ENTER
NodeTreeView::NodeTreeView(QWidget *parent) :
QTreeWidget(parent),
only_show_keyframable_(false)
{
connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged);
Retranslate();
}
bool NodeTreeView::IsNodeEnabled(Node *n) const
{
return !disabled_nodes_.contains(n);
}
bool NodeTreeView::IsInputEnabled(NodeInput *i) const
{
return !disabled_inputs_.contains(i);
}
void NodeTreeView::SetNodes(const QList<Node *> &nodes)
{
nodes_ = nodes;
this->clear();
foreach (Node* n, nodes_) {
QTreeWidgetItem* node_item = new QTreeWidgetItem();
node_item->setText(0, n->Name());
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
node_item->setData(0, kItemType, kItemTypeNode);
node_item->setData(0, kItemPointer, reinterpret_cast<quintptr>(n));
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
if (only_show_keyframable_ && !i->is_keyframable()) {
continue;
}
QTreeWidgetItem* input_item = new QTreeWidgetItem(node_item);
input_item->setText(0, i->name());
input_item->setCheckState(0, disabled_inputs_.contains(i) ? Qt::Unchecked : Qt::Checked);
input_item->setData(0, kItemType, kItemTypeInput);
input_item->setData(0, kItemPointer, reinterpret_cast<quintptr>(i));
}
// Add at the end to prevent unnecessary signalling while we're setting these objects up
if (node_item->childCount() > 0) {
this->addTopLevelItem(node_item);
} else {
delete node_item;
}
}
}
void NodeTreeView::changeEvent(QEvent *e)
{
QTreeWidget::changeEvent(e);
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
}
void NodeTreeView::Retranslate()
{
setHeaderLabel(tr("Nodes"));
}
void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column)
{
Q_UNUSED(column)
switch (item->data(0, kItemType).toInt()) {
case kItemTypeNode:
{
Node* n = reinterpret_cast<Node*>(item->data(0, kItemPointer).value<quintptr>());
if (item->checkState(0) == Qt::Checked) {
if (disabled_nodes_.contains(n)) {
disabled_nodes_.removeOne(n);
emit NodeEnableChanged(n, true);
}
} else if (!disabled_nodes_.contains(n)) {
disabled_nodes_.append(n);
emit NodeEnableChanged(n, false);
}
break;
}
case kItemTypeInput:
{
NodeInput* i = reinterpret_cast<NodeInput*>(item->data(0, kItemPointer).value<quintptr>());
if (item->checkState(0) == Qt::Checked) {
if (disabled_inputs_.contains(i)) {
disabled_inputs_.removeOne(i);
emit InputEnableChanged(i, true);
}
} else if (!disabled_inputs_.contains(i)) {
disabled_inputs_.append(i);
emit InputEnableChanged(i, false);
}
break;
}
}
}
OLIVE_NAMESPACE_EXIT
+62
View File
@@ -0,0 +1,62 @@
#ifndef NODETREEVIEW_H
#define NODETREEVIEW_H
#include <QTreeWidget>
#include "node/node.h"
OLIVE_NAMESPACE_ENTER
class NodeTreeView : public QTreeWidget
{
Q_OBJECT
public:
NodeTreeView(QWidget *parent = nullptr);
bool IsNodeEnabled(Node* n) const;
bool IsInputEnabled(NodeInput* i) const;
void SetOnlyShowKeyframable(bool e)
{
only_show_keyframable_ = e;
}
public slots:
void SetNodes(const QList<Node*>& nodes);
signals:
void NodeEnableChanged(Node* n, bool e);
void InputEnableChanged(NodeInput* i, bool e);
protected:
virtual void changeEvent(QEvent* e) override;
private:
void Retranslate();
enum ItemType {
kItemTypeNode,
kItemTypeInput
};
static const int kItemType = Qt::UserRole;
static const int kItemPointer = Qt::UserRole + 1;
QList<Node*> nodes_;
QList<Node*> disabled_nodes_;
QList<NodeInput*> disabled_inputs_;
bool only_show_keyframable_;
private slots:
void ItemCheckStateChanged(QTreeWidgetItem* item, int column);
};
OLIVE_NAMESPACE_EXIT
#endif // NODETREEVIEW_H
+32 -148
View File
@@ -57,6 +57,10 @@ NodeView::NodeView(QWidget *parent) :
scene_.setSceneRect(-1000000, -1000000, 2000000, 2000000);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
select_blocks_internal_timer_.setSingleShot(true);
select_blocks_internal_timer_.setInterval(100);
connect(&select_blocks_internal_timer_, &QTimer::timeout, this, &NodeView::SelectBlocksInternal);
}
NodeView::~NodeView()
@@ -200,13 +204,21 @@ void NodeView::SelectWithDependencies(QList<Node *> nodes)
void NodeView::SelectBlocks(const QList<Block *> &blocks)
{
if (!graph_) {
return;
}
selected_blocks_.append(blocks);
SelectBlocksInternal();
QueueSelectBlocksInternal();
}
void NodeView::DeselectBlocks(const QList<Block *> &blocks)
{
if (!graph_) {
return;
}
// Remove temporary associations
foreach (Block* b, selected_blocks_) {
if (!blocks.contains(b)) {
@@ -219,7 +231,7 @@ void NodeView::DeselectBlocks(const QList<Block *> &blocks)
selected_blocks_.removeOne(b);
}
SelectBlocksInternal();
QueueSelectBlocksInternal();
}
void NodeView::CopySelected(bool cut)
@@ -372,8 +384,8 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
QRect edge_detect_rect(event->pos(), event->pos());
// FIXME: Hardcoded numbers
edge_detect_rect.adjust(-20, -20, 20, 20);
int edge_detect_radius = fontMetrics().height();
edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius);
QList<QGraphicsItem*> items = this->items(edge_detect_rect);
@@ -653,140 +665,6 @@ void NodeView::ContextMenuFilterChanged(QAction *action)
}
}
void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos)
{
QRectF destination_rect = n->rect();
destination_rect.translate(n->pos());
double x_movement = destination_rect.width() * 1.5;
double y_movement = destination_rect.height() * 1.5;
QList<QGraphicsItem*> items = scene()->items(destination_rect);
n->setPos(pos);
foreach (QGraphicsItem* item, items) {
if (item == n) {
continue;
}
NodeViewItem* node_item = dynamic_cast<NodeViewItem*>(item);
if (!node_item) {
continue;
}
qDebug() << "Moving" << node_item->GetNode() << "for" << n->GetNode();
QPointF new_pos;
if (item->pos() == pos) {
qDebug() << "Same pos, need more info";
// Item positions are exact, we'll need more information to determine where this item should go
Node* ours = n->GetNode();
Node* theirs = node_item->GetNode();
bool moved = false;
new_pos = item->pos();
// Heuristic to determine whether to move the other item above or below
foreach (NodeEdgePtr our_edge, ours->output()->edges()) {
foreach (NodeEdgePtr their_edge, theirs->output()->edges()) {
if (our_edge->output()->parentNode() == their_edge->output()->parentNode()) {
qDebug() << " They share a node that they output to";
if (our_edge->input()->index() > their_edge->input()->index()) {
// Their edge should go above ours
qDebug() << " Our edge goes BELOW theirs";
new_pos.setY(new_pos.y() - y_movement);
} else {
// Our edge should go below ours
qDebug() << " Our edge goes ABOVE theirs";
new_pos.setY(new_pos.y() + y_movement);
}
moved = true;
break;
}
}
}
// If we find anything, just move at random
if (!moved) {
new_pos.setY(new_pos.y() - y_movement);
}
} else if (item->pos().x() == pos.x()) {
qDebug() << "Same X, moving vertically";
// Move strictly up or down
new_pos = item->pos();
if (item->pos().y() < pos.y()) {
// Move further up
new_pos.setY(pos.y() - y_movement);
} else {
// Move further down
new_pos.setY(pos.y() + y_movement);
}
} else if (item->pos().y() == pos.y()) {
qDebug() << "Same Y, moving horizontally";
// Move strictly left or right
new_pos = item->pos();
if (item->pos().x() < pos.x()) {
// Move further up
new_pos.setX(pos.x() - x_movement);
} else {
// Move further down
new_pos.setX(pos.x() + x_movement);
}
} else {
qDebug() << "Diff pos, pushing in angle";
// The item does not have equal X or Y, attempt to push it away from `pos` in the direction it's in
double x_diff = item->pos().x() - pos.x();
double y_diff = item->pos().y() - pos.y();
double slope = y_diff / x_diff;
double y_int = item->pos().y() - slope * item->pos().x();
if (qAbs(slope) > 1.0) {
// Vertical difference is greater than horizontal difference, prioritize vertical movement
double desired_y = pos.y();
if (item->pos().y() > pos.y()) {
desired_y += y_movement;
} else {
desired_y -= y_movement;
}
double x = (desired_y - y_int) / slope;
new_pos = QPointF(x, desired_y);
} else {
// Horizontal difference is greater than vertical difference, prioritize horizontal movement
double desired_x = pos.x();
if (item->pos().x() > pos.x()) {
desired_x += x_movement;
} else {
desired_x -= x_movement;
}
double y = slope * desired_x + y_int;
new_pos = QPointF(desired_x, y);
}
}
PlaceNode(node_item, new_pos);
}
}
void NodeView::AttachNodesToCursor(const QList<Node *> &nodes)
{
QList<NodeViewItem*> items;
@@ -851,7 +729,7 @@ void NodeView::UpdateBlockFilter()
}
bool first = true;
QRectF last_rect;
QPointF last_bottom_right;
QList<Node*> currently_visible;
@@ -883,10 +761,13 @@ void NodeView::UpdateBlockFilter()
if (first) {
first = false;
} else {
QPointF desired_anchor_pos = last_rect.bottomRight() + QPointF(0, 2);
QPointF desired_anchor_pos = last_bottom_right + QPointF(0, 2);
QPointF necessary_movement = anchor.topRight() - desired_anchor_pos;
// Calculate the bottom right from which we'll anchor the next rect
last_bottom_right = anchor.bottomRight() - necessary_movement;
b->SetPosition(b->GetPosition() - necessary_movement);
foreach (Node* d, deps) {
d->SetPosition(d->GetPosition() - necessary_movement);
@@ -915,15 +796,12 @@ void NodeView::UpdateBlockFilter()
// And lastly, add them all to our currently visible list
currently_visible.append(deps);
// Cache this rect so we can calculate other rects
last_rect = anchor;
}
// Show only edges between those dependencies
foreach (NodeViewEdge* edge, scene_.edge_map()) {
edge->setVisible((currently_visible.contains(edge->edge()->input()->parentNode())
&& currently_visible.contains(edge->edge()->output()->parentNode())));
edge->setVisible((currently_visible.contains(edge->edge()->input_node())
&& currently_visible.contains(edge->edge()->output_node())));
}
}
@@ -942,10 +820,16 @@ void NodeView::DisassociateNode(Node *n, bool remove_from_map)
disconnect(n, &Node::destroyed, this, &NodeView::AssociatedNodeDestroyed);
}
void NodeView::QueueSelectBlocksInternal()
{
select_blocks_internal_timer_.stop();
select_blocks_internal_timer_.start();
}
void NodeView::SelectBlocksInternal()
{
// Block scene signals while our selection is changing a lot
scene_.blockSignals(true);
DisconnectSelectionChangedSignal();
if (filter_mode_ == kFilterShowSelectedBlocks) {
UpdateBlockFilter();
@@ -962,7 +846,7 @@ void NodeView::SelectBlocksInternal()
SelectWithDependencies(nodes);
// Stop blocking signals and send a change signal now that all of our processing is done
scene_.blockSignals(false);
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
if (!selected_blocks_.isEmpty()) {
@@ -1024,7 +908,7 @@ void NodeView::GraphEdgeRemoved(NodeEdgePtr edge)
{
scene_.RemoveEdge(edge);
Node* output_node = edge->output()->parentNode();
Node* output_node = edge->output_node();
// Check if this disconnected node still connects to a selected block, in which case do nothing
foreach (Block* b, selected_blocks_) {
+5 -5
View File
@@ -84,11 +84,7 @@ protected:
virtual void wheelEvent(QWheelEvent* event) override;
//virtual void scrollContentsBy(int dx, int dy) override;
private:
void PlaceNode(NodeViewItem* n, const QPointF& pos);
void AttachNodesToCursor(const QList<Node*>& nodes);
void AttachItemsToCursor(const QList<NodeViewItem*>& items);
@@ -107,7 +103,9 @@ private:
void AssociateNodeWithSelectedBlocks(Node* n);
void DisassociateNode(Node* n, bool remove_from_map);
void SelectBlocksInternal();
void QueueSelectBlocksInternal();
QTimer select_blocks_internal_timer_;
NodeGraph* graph_;
@@ -188,6 +186,8 @@ private slots:
*/
void ContextMenuFilterChanged(QAction* action);
void SelectBlocksInternal();
};
OLIVE_NAMESPACE_EXIT
+29 -5
View File
@@ -261,8 +261,25 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
Color node_color = Config::Current()[QStringLiteral("NodeCatColor%1")
.arg(node_->Category().first())].value<Color>();
QLinearGradient grad;
grad.setStart(0, rect().top());
grad.setFinalStop(0, rect().bottom());
QColor node_color_as_qcolor = node_color.toQColor();
{
// Generate lighter color for gradient
qreal hue, sat, lightness;
node_color_as_qcolor.getHslF(&hue, &sat, &lightness);
lightness = qMin(1.0, lightness + 0.2);
grad.setColorAt(0.0, QColor::fromHslF(hue, sat, lightness));
}
grad.setColorAt(1.0, node_color_as_qcolor);
painter->setPen(Qt::black);
painter->setBrush(node_color.toQColor());
painter->setBrush(grad);
painter->drawRect(title_bar_rect_);
@@ -270,10 +287,17 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
QString node_label;
if (node_->GetLabel().isEmpty()) {
node_label = node_->ShortName();
} else {
if (!node_->GetLabel().isEmpty()) {
// Use label directly if node has one
node_label = node_->GetLabel();
} else if (node_->IsTrack()) {
// If node is a track, use a special track name to help users identify better
// Exception for tracks
TrackOutput* track = static_cast<TrackOutput*>(node_);
node_label = TrackOutput::GetDefaultTrackName(track->track_type(), track->Index());
} else {
// Otherwise, just use the node's short name
node_label = node_->ShortName();
}
QFont f;
@@ -308,7 +332,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
}
// Determine the text color (automatically calculate from node background color)
if (node_color.GetRoughLuminance() > 0.66) {
if (node_color.GetRoughLuminance() > 0.5) {
painter->setPen(Qt::black);
} else {
painter->setPen(Qt::white);
+16
View File
@@ -154,6 +154,15 @@ void NodeRemoveCommand::redo_internal()
// Take nodes from graph (TakeNode() will automatically disconnect edges)
foreach (Node* n, nodes_) {
// If the node is a block, unlink any linked blocks before removing
if (n->IsBlock()) {
Block *b = static_cast<Block *>(n);
if (b->HasLinks()) {
BlockUnlinkAllCommand *unlink_command = new BlockUnlinkAllCommand(b);
unlink_command->redo();
block_unlink_commands_.append(unlink_command);
}
}
graph_->TakeNode(n, &memory_manager_);
}
}
@@ -165,12 +174,19 @@ void NodeRemoveCommand::undo_internal()
graph_->AddNode(n);
}
// Relink any blocks that were unlinked
foreach(BlockUnlinkAllCommand* command, block_unlink_commands_) {
command->undo();
delete command;
}
// Re-connect edges
foreach (NodeEdgePtr edge, edges_) {
NodeParam::ConnectEdge(edge->output(), edge->input());
}
edges_.clear();
block_unlink_commands_.clear();
}
Project *NodeRemoveCommand::GetRelevantProject() const
+60
View File
@@ -27,6 +27,7 @@
#include "node/node.h"
#include "nodeviewitem.h"
#include "undo/undocommand.h"
#include "widget/timelinewidget/undo/undo.h"
OLIVE_NAMESPACE_ENTER
@@ -112,6 +113,7 @@ private:
NodeGraph* graph_;
QList<Node*> nodes_;
QList<NodeEdgePtr> edges_;
QList<BlockUnlinkAllCommand*> block_unlink_commands_;
};
class NodeRemoveWithExclusiveDeps : public UndoCommand {
@@ -149,6 +151,64 @@ private:
};
class NodeGraphBeginOperationCommand : public UndoCommand {
public:
NodeGraphBeginOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->BeginOperation();
}
virtual void undo_internal() override
{
graph_->EndOperation();
}
private:
NodeGraph* graph_;
};
class NodeGraphEndOperationCommand : public UndoCommand {
public:
NodeGraphEndOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->EndOperation();
}
virtual void undo_internal() override
{
graph_->BeginOperation();
}
private:
NodeGraph* graph_;
};
OLIVE_NAMESPACE_EXIT
#endif // NODEVIEWUNDO_H
@@ -161,7 +161,10 @@ void PlaybackControls::SetTimebase(const rational &r)
time_base_ = r;
cur_tc_lbl_->SetTimebase(r);
cur_tc_lbl_->setEnabled(!r.isNull());
cur_tc_lbl_->setVisible(!r.isNull());
end_tc_lbl_->setVisible(!r.isNull());
setEnabled(!r.isNull());
}
void PlaybackControls::SetAudioVideoDragButtonsVisible(bool e)
@@ -32,5 +32,7 @@ set(OLIVE_SOURCES
widget/projectexplorer/projectexplorericonviewitemdelegate.cpp
widget/projectexplorer/projectexplorernavigation.h
widget/projectexplorer/projectexplorernavigation.cpp
widget/projectexplorer/projectexplorerundo.h
widget/projectexplorer/projectexplorerundo.cpp
PARENT_SCOPE
)
+125 -6
View File
@@ -23,6 +23,7 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QUrl>
#include <QVBoxLayout>
@@ -31,11 +32,14 @@
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "projectexplorerundo.h"
#include "task/precache/precachetask.h"
#include "task/taskmanager.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -536,6 +540,30 @@ void ProjectExplorer::DeselectAll()
CurrentView()->selectionModel()->clearSelection();
}
QList<MediaInput *> ProjectExplorer::GetMediaNodesUsingFootage(Footage *item)
{
QList<MediaInput *> list;
// Get all sequences.
QList<ItemPtr> sequences = model_.project()->get_items_of_type(Item::kSequence);
// Footage can contain multiple streams, all of which need to be dealt with
foreach (ItemPtr s, sequences) {
const QList<Node*>& nodes = static_cast<Sequence*>(s.get())->nodes();
foreach (Node* n, nodes) {
if (n->IsMedia()) {
MediaInput* media_node = static_cast<MediaInput*>(n);
if (media_node->footage()->footage() == item) {
list.append(media_node);
}
}
}
}
return list;
}
void ProjectExplorer::DeleteSelected()
{
QList<Item*> selected = SelectedItems();
@@ -547,18 +575,109 @@ void ProjectExplorer::DeleteSelected()
QUndoCommand* command = new QUndoCommand();
foreach (Item* item, selected) {
ItemPtr item_ptr = item->get_shared_ptr();
// If this is a sequence, close it
if (item_ptr->type() == Item::kSequence) {
Sequence* s = static_cast<Sequence*>(item_ptr.get());
// Verify whether this item is in use anywhere
switch (item->type()) {
case Item::kSequence:
{
// If this is a sequence, check if it's open and close it if necessary
Sequence* s = static_cast<Sequence*>(item);
if (Core::instance()->main_window()->IsSequenceOpen(s)) {
Core::instance()->main_window()->CloseSequence(s);
}
break;
}
case Item::kFootage:
{
// If this is footage, check if it's used anywhere in any sequence
Footage* footage = static_cast<Footage*>(item);
QList<MediaInput*> footage_nodes = GetMediaNodesUsingFootage(footage);
if (!footage_nodes.isEmpty()) {
// Footage is in use, show messagebox asking what to do about it
QList<Sequence*> used_in_sequences;
// Compile list of sequences to assist the user in making this decision
foreach (MediaInput* i, footage_nodes) {
Sequence* media_parent = static_cast<Sequence*>(i->parent());
if (!used_in_sequences.contains(media_parent)) {
used_in_sequences.append(media_parent);
}
}
QString sequence_list_str;
foreach (Sequence* s, used_in_sequences) {
sequence_list_str.append(QStringLiteral("%1\n").arg(s->name()));
}
QMessageBox msgbox(this);
msgbox.setWindowTitle(tr("Confirm Footage Deletion"));
msgbox.setText(tr("The footage \"%1\" is currently used in the following sequence(s):\n\n"
"%2\nWhat would you like to do with these clips?")
.arg(footage->filename(), sequence_list_str));
msgbox.setIcon(QMessageBox::Warning);
// Set up buttons
QPushButton* offline_btn = msgbox.addButton(tr("Offline Footage"), QMessageBox::YesRole);
QPushButton* delete_clip_btn = msgbox.addButton(tr("Delete Clips"), QMessageBox::NoRole);
msgbox.addButton(QMessageBox::Cancel);
// Run messagebox
msgbox.exec();
if (msgbox.clickedButton() == offline_btn || msgbox.clickedButton() == delete_clip_btn) {
// For safety, even if we're deleting clips, we'll offline the footage nodes too
new OfflineFootageCommand(footage_nodes, command);
}
if (msgbox.clickedButton() == delete_clip_btn) {
// Delete any blocks that use this footage
QList<Block*> blocks_to_remove;
foreach (Sequence* s, used_in_sequences) {
foreach (TrackOutput* track, s->viewer_output()->GetTracks()) {
foreach (Block* b, track->Blocks()) {
QList<Node*> deps = b->GetDependencies();
foreach (MediaInput* i, footage_nodes) {
if (deps.contains(i)) {
blocks_to_remove.append(b);
break;
}
}
}
}
}
TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command);
} else if (msgbox.clickedButton() != offline_btn) {
// Must have cancelled
delete command;
return;
}
}
// Close footage if currently open in footage panel
FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType<FootageViewerPanel>().first();
if (footage_panel->GetSelectedFootage().contains(footage)) {
footage_panel->SetFootage(nullptr);
}
break;
}
case Item::kFolder:
// Do nothing
break;
}
new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command);
new ProjectViewModel::RemoveItemCommand(&model_, item->get_shared_ptr(), command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
@@ -25,6 +25,7 @@
#include <QTimer>
#include <QTreeView>
#include "node/input/media/media.h"
#include "project/project.h"
#include "project/projectviewmodel.h"
#include "widget/projectexplorer/projectexplorericonview.h"
@@ -101,6 +102,18 @@ signals:
void DoubleClickedItem(Item* item);
private:
/**
* @brief Check if an item is in use anywhere and return any relevant input nodes
*/
QList<MediaInput*> GetMediaNodesUsingFootage(Footage* item);
/**
* @brief Get all the blocks that solely rely on an input node
*
* Ignores blocks that depend on multiple inputs
*/
QList<Block*> GetFootageBlocks(QList<Node*> nodes);
/**
* @brief Simple convenience function for adding a view to this stacked widget
*
@@ -0,0 +1,54 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "projectexplorerundo.h"
OLIVE_NAMESPACE_ENTER
OfflineFootageCommand::OfflineFootageCommand(const QList<MediaInput *> &media, QUndoCommand* parent) :
UndoCommand(parent)
{
foreach (MediaInput* i, media) {
stream_data_.insert(i, i->footage());
}
project_ = static_cast<Sequence*>(media.first()->parent())->project();
}
Project *OfflineFootageCommand::GetRelevantProject() const
{
return project_;
}
void OfflineFootageCommand::redo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(nullptr);
}
}
void OfflineFootageCommand::undo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(it.value());
}
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,52 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef PROJECTEXPLORERUNDO_H
#define PROJECTEXPLORERUNDO_H
#include "node/input/media/media.h"
#include "undo/undocommand.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief An undo command for offlining footage when it is deleted from the project explorer
*/
class OfflineFootageCommand : public UndoCommand {
public:
OfflineFootageCommand(const QList<MediaInput*>& media, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
QMap<MediaInput*, StreamPtr> stream_data_;
Project* project_;
};
OLIVE_NAMESPACE_EXIT
#endif // PROJECTEXPLORERUNDO_H
+2
View File
@@ -29,5 +29,7 @@ set(OLIVE_SOURCES
widget/timelinewidget/timelinescaledobject.cpp
widget/timelinewidget/timelinewidget.h
widget/timelinewidget/timelinewidget.cpp
widget/timelinewidget/timelinewidgetselections.h
widget/timelinewidget/timelinewidgetselections.cpp
PARENT_SCOPE
)
@@ -48,10 +48,10 @@ public:
static double CalculateScaleFromDimensions(double viewport_sz, double content_sz);
static double CalculatePaddingFromDimensionScale(double viewport_sz);
protected:
double TimeToScene(const rational& time);
rational SceneToTime(const double &x, bool round = false);
protected:
virtual void TimebaseChangedEvent(const rational&){}
virtual void ScaleChangedEvent(const double&){}
+267 -158
View File
@@ -30,6 +30,17 @@
#include "common/timecodefunctions.h"
#include "dialog/sequence/sequence.h"
#include "node/block/transition/transition.h"
#include "tool/add.h"
#include "tool/beam.h"
#include "tool/edit.h"
#include "tool/pointer.h"
#include "tool/razor.h"
#include "tool/ripple.h"
#include "tool/rolling.h"
#include "tool/slide.h"
#include "tool/slip.h"
#include "tool/transition.h"
#include "tool/zoom.h"
#include "tool/tool.h"
#include "trackview/trackview.h"
#include "widget/menu/menu.h"
@@ -107,6 +118,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->SetSnapService(this);
view->SetSelectionList(&selections_);
view->SetGhostList(&ghost_items_);
view_splitter_->addWidget(tview);
@@ -149,6 +162,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
SetMaximumScale(TimelineViewBase::kMaximumScale);
SetAutoSetTimebase(false);
connect(Core::instance(), &Core::ToolChanged, this, &TimelineWidget::ToolChanged);
}
TimelineWidget::~TimelineWidget()
@@ -161,20 +176,16 @@ TimelineWidget::~TimelineWidget()
void TimelineWidget::Clear()
{
QList<Block*> deselected_blocks;
QMap<Block*, TimelineViewBlockItem*>::const_iterator iterator;
for (iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) {
if (iterator.value()->isSelected()) {
deselected_blocks.append(iterator.key());
}
// Delete all items
for (auto iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) {
delete iterator.value();
}
block_items_.clear();
emit BlocksDeselected(deselected_blocks);
// Emit that we've deselected any selected blocks
SignalDeselectedAllBlocks();
// Set null timebase
SetTimebase(0);
}
@@ -224,10 +235,6 @@ void TimelineWidget::ScaleChangedEvent(const double &scale)
}
}
foreach (TimelineViewGhostItem* ghost, ghost_items_) {
ghost->SetScale(scale);
}
foreach (TimelineAndTrackView* view, views_) {
view->view()->SetScale(scale);
}
@@ -271,6 +278,10 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n)
disconnect(n, &ViewerOutput::TimebaseChanged, this, &TimelineWidget::SetTimebase);
disconnect(n, &ViewerOutput::TrackHeightChanged, this, &TimelineWidget::TrackHeightChanged);
foreach (TrackOutput* track, n->GetTracks()) {
RemoveTrack(track);
}
ruler()->SetPlaybackCache(nullptr);
SetTimebase(0);
@@ -341,22 +352,6 @@ void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, X
}
}
TimelineWidget::DraggedFootage TimelineWidget::FootageToDraggedFootage(Footage *f)
{
return DraggedFootage(f, f->get_enabled_stream_flags());
}
QList<TimelineWidget::DraggedFootage> TimelineWidget::FootageToDraggedFootage(QList<Footage *> footage)
{
QList<DraggedFootage> df;
foreach (Footage* f, footage) {
df.append(FootageToDraggedFootage(f));
}
return df;
}
rational TimelineWidget::GetToolTipTimebase() const
{
if (GetConnectedNode() && use_audio_time_units_) {
@@ -367,34 +362,28 @@ rational TimelineWidget::GetToolTipTimebase() const
void TimelineWidget::SelectAll()
{
QList<Block*> blocks_selected;
QList<Block*> newly_selected_blocks;
QMap<Block*, TimelineViewBlockItem*>::const_iterator i;
for (i=block_items_.constBegin(); i!=block_items_.end(); i++) {
if (!i.value()->isSelected()) {
i.value()->setSelected(true);
blocks_selected.append(i.key());
for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) {
if (!selected_blocks_.contains(it.key())) {
newly_selected_blocks.append(it.key());
AddSelection(it.key()->range(), it.value()->Track());
}
}
emit BlocksSelected(blocks_selected);
SignalSelectedBlocks(newly_selected_blocks, false);
}
void TimelineWidget::DeselectAll()
{
QList<Block*> blocks_deselected;
// Clear selections
selections_.clear();
QMap<Block*, TimelineViewBlockItem*>::const_iterator i;
// Update all viewports
UpdateViewports();
for (i=block_items_.constBegin(); i!=block_items_.end(); i++) {
if (i.value()->isSelected()) {
i.value()->setSelected(false);
blocks_deselected.append(i.key());
}
}
emit BlocksDeselected(blocks_deselected);
// Clear list and emit signal
SignalDeselectedAllBlocks();
}
void TimelineWidget::RippleToIn()
@@ -486,8 +475,6 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QList<Block *> &blocks,
new TrackReplaceBlockWithGapCommand(original_track, b, command);
if (remove_from_graph) {
new BlockUnlinkAllCommand(b, command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(b->parent()), b, command);
}
}
@@ -497,16 +484,11 @@ void TimelineWidget::DeleteSelected(bool ripple)
{
QList<TimelineViewBlockItem *> selected_list = GetSelectedBlocks();
QList<Block*> blocks_to_delete;
QList<TrackReference> tracks_affected;
foreach (TimelineViewBlockItem* item, selected_list) {
Block* b = item->block();
blocks_to_delete.append(b);
if (!tracks_affected.contains(item->Track())) {
tracks_affected.append(item->Track());
}
}
// No-op if nothing is selected
@@ -541,6 +523,9 @@ void TimelineWidget::DeleteSelected(bool ripple)
// Replace clips with gaps (effectively deleting them)
ReplaceBlocksWithGaps(clips_to_delete, true, command);
// Remove all selections
new TimelineSetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), command);
// Insert ripple command now that it's all cleaned up gaps
if (ripple) {
TimeRangeList range_list;
@@ -767,16 +752,8 @@ QList<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
{
QList<TimelineViewBlockItem *> list;
QMapIterator<Block*, TimelineViewBlockItem*> iterator(block_items_);
while (iterator.hasNext()) {
iterator.next();
TimelineViewBlockItem* item = iterator.value();
if (item && item->isSelected()) {
list.append(item);
}
foreach (Block* b, selected_blocks_) {
list.append(block_items_.value(b));
}
return list;
@@ -825,12 +802,7 @@ void TimelineWidget::ClearGhosts()
HideSnaps();
}
bool TimelineWidget::HasGhosts()
{
return !ghost_items_.isEmpty();
}
TimelineWidget::Tool *TimelineWidget::GetActiveTool()
TimelineTool *TimelineWidget::GetActiveTool()
{
return tools_.at(Core::instance()->tool());
}
@@ -857,7 +829,7 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event)
active_tool_->MouseMove(event);
} else {
// Mouse is not down, attempt a hover event
Tool* hover_tool = GetActiveTool();
TimelineTool* hover_tool = GetActiveTool();
if (hover_tool) {
hover_tool->HoverMove(event);
@@ -876,9 +848,8 @@ void TimelineWidget::ViewMouseReleased(TimelineViewMouseEvent *event)
void TimelineWidget::ViewMouseDoubleClicked(TimelineViewMouseEvent *event)
{
if (GetConnectedNode() && active_tool_ != nullptr) {
active_tool_->MouseDoubleClick(event);
active_tool_ = nullptr;
if (GetConnectedNode()) {
GetActiveTool()->MouseDoubleClick(event);
}
}
@@ -905,40 +876,72 @@ void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event)
void TimelineWidget::AddBlock(Block *block, TrackReference track)
{
// Set up clip with view parameters (clip item will automatically size its rect accordingly)
TimelineViewBlockItem* item = new TimelineViewBlockItem(block);
TimelineViewBlockItem* item = block_items_.value(block);
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetScale(GetScale());
item->SetTrack(track);
item->SetTimebase(timebase());
if (!item) {
// Add to list of clip items that can be iterated through
block_items_.insert(block, item);
// Add to list of clip items that can be iterated through
item = new TimelineViewBlockItem(block);
block_items_.insert(block, item);
// Add item to graphics scene
views_.at(track.type())->view()->scene()->addItem(item);
// Set scale parameters
item->SetScale(GetScale());
item->SetTimebase(timebase());
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetTrack(track);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
// Add item to graphics scene
views_.at(track.type())->view()->scene()->addItem(item);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
} else if (item->Track() != track) {
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetTrack(track);
}
}
void TimelineWidget::RemoveBlock(Block *block)
void TimelineWidget::RemoveBlock(const QList<Block *> &blocks)
{
disconnect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
disconnect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
disconnect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
disconnect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
QList<TimelineViewBlockItem*> delete_items;
delete_items.reserve(blocks.size());
TimelineViewBlockItem* item = block_items_.take(block);
QList<Block*> deselect_blocks;
if (item->isSelected()) {
// Sending a list of one item all the time is not very efficient
emit BlocksDeselected({block});
foreach (Block* b, blocks) {
// Disconnect all signals
disconnect(b, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
disconnect(b, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
disconnect(b, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
disconnect(b, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
// Take item from map
TimelineViewBlockItem* item = block_items_.take(b);
// If selected, deselect it
int select_index = selected_blocks_.indexOf(b);
if (select_index > -1) {
selected_blocks_.removeAt(select_index);
deselect_blocks.append(b);
RemoveSelection(item);
}
// Finally, delete item
delete item;
}
delete item;
if (!deselect_blocks.isEmpty()) {
// We already removed the blocks from selected_blocks_, so we can signal directly rather than
// through
emit BlocksDeselected(deselect_blocks);
}
qDeleteAll(delete_items);
}
void TimelineWidget::AddTrack(TrackOutput *track, Timeline::TrackType type)
@@ -956,9 +959,7 @@ void TimelineWidget::RemoveTrack(TrackOutput *track)
disconnect(track, &TrackOutput::IndexChanged, this, &TimelineWidget::TrackIndexChanged);
disconnect(track, &TrackOutput::PreviewChanged, this, &TimelineWidget::TrackPreviewUpdated);
foreach (Block* b, track->Blocks()) {
RemoveBlock(b);
}
RemoveBlock(track->Blocks());
}
void TimelineWidget::TrackIndexChanged()
@@ -1142,11 +1143,16 @@ void TimelineWidget::ViewTimestampChanged(int64_t ts)
emit TimeChanged(ts);
}
void TimelineWidget::ToolChanged()
{
HideSnaps();
}
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
{
ghost->SetScale(GetScale());
ghost_items_.append(ghost);
views_.at(ghost->Track().type())->view()->scene()->addItem(ghost);
UpdateViewports(ghost->GetTrack().type());
}
void TimelineWidget::UpdateViewTimebases()
@@ -1174,12 +1180,80 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected)
TimelineViewBlockItem* link_item;
foreach (Block* link, block->linked_clips()) {
if ((link_item = block_items_[link]) != nullptr) {
link_item->setSelected(selected);
link_item = block_items_.value(link);
if (link_item) {
if (selected) {
AddSelection(link_item);
} else {
RemoveSelection(link_item);
}
}
}
}
void TimelineWidget::QueueScroll(int value)
{
// (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated)
deferred_scroll_value_ = value;
QTimer::singleShot(0, this, &TimelineWidget::DeferredScrollAction);
}
TimelineView *TimelineWidget::GetFirstTimelineView()
{
return views_.first()->view();
}
const QRect& TimelineWidget::GetRubberBandGeometry() const
{
return rubberband_.geometry();
}
void TimelineWidget::SignalSelectedBlocks(QList<Block *> input, bool filter)
{
if (input.isEmpty()) {
return;
}
if (filter) {
// If filtering, remove all the blocks that are already selected
for (int i=0; i<input.size(); i++) {
Block* b = input.at(i);
if (selected_blocks_.contains(b)) {
input.removeAt(i);
i--;
}
}
}
selected_blocks_.append(input);
emit BlocksSelected(input);
}
void TimelineWidget::SignalDeselectedBlocks(const QList<Block *> &deselected_blocks)
{
if (deselected_blocks.isEmpty()) {
return;
}
foreach (Block* b, deselected_blocks) {
selected_blocks_.removeOne(b);
}
emit BlocksDeselected(deselected_blocks);
}
void TimelineWidget::SignalDeselectedAllBlocks()
{
if (!selected_blocks_.isEmpty()) {
emit BlocksDeselected(selected_blocks_);
selected_blocks_.clear();
}
}
QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& playhead_time,
Timeline::MovementMode mode)
{
@@ -1321,6 +1395,17 @@ void TimelineWidget::ShowSnap(const QList<rational> &times)
}
}
void TimelineWidget::UpdateViewports(const Timeline::TrackType &type)
{
if (type == Timeline::kTrackTypeNone) {
foreach (TimelineAndTrackView* tview, views_) {
tview->view()->viewport()->update();
}
} else {
views_.at(type)->view()->viewport()->update();
}
}
void TimelineWidget::HideSnaps()
{
foreach (TimelineAndTrackView* tview, views_) {
@@ -1344,12 +1429,8 @@ void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_li
rubberband_.show();
// We don't touch any blocks that are already selected. If you want these to be deselected by
// default, call DeselectAll() befoer calling StartRubberBandSelect()
foreach (TimelineViewBlockItem* block, block_items_) {
if (block->isSelected()) {
rubberband_already_selected_.append(block);
}
}
// default, call DeselectAll() before calling StartRubberBandSelect()
rubberband_old_selections_ = selections_;
MoveRubberBandSelect(enable_selecting, select_links);
}
@@ -1364,7 +1445,7 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
return;
}
QList<QGraphicsItem*> new_selected_list;
QList<QGraphicsItem*> items_in_rubberband;
// Determine all items in the rubberband
foreach (TimelineAndTrackView* tview, views_) {
@@ -1377,60 +1458,45 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
// Normalize and get items in rect
QList<QGraphicsItem*> rubberband_items = view->items(mapped_rect.normalized());
new_selected_list.append(rubberband_items);
items_in_rubberband.append(rubberband_items);
}
// Filter out any items that were already selected
if (!rubberband_already_selected_.isEmpty()) {
for (int i=0; i<new_selected_list.size(); i++) {
if (rubberband_already_selected_.contains(new_selected_list.at(i))) {
new_selected_list.removeAt(i);
i--;
}
}
}
// Reset selection to whatever it was before
SetSelections(rubberband_old_selections_);
foreach (QGraphicsItem* item, rubberband_now_selected_) {
item->setSelected(false);
}
// Add any blocks in rubberband
rubberband_now_selected_.clear();
// Cache limit because we append to this array in this loop and don't need to process those
int lim = new_selected_list.size();
for (int i=0;i<lim;i++) {
TimelineViewBlockItem* block_item = static_cast<TimelineViewBlockItem*>(new_selected_list.at(i));
if (block_item->block()->type() == Block::kGap) {
continue;
}
foreach (QGraphicsItem* item, items_in_rubberband) {
TimelineViewBlockItem* block_item = dynamic_cast<TimelineViewBlockItem*>(item);
TrackOutput* t = GetTrackFromReference(block_item->Track());
if (t && t->IsLocked()) {
continue;
}
// Since new_selected_list is filtered by rubberband_already_selected_, this should certainly
// be deselected by now
block_item->setSelected(true);
if (select_links) {
// Select the block's links
if (block_item) {
Block* b = block_item->block();
// Add its links to the list
TimelineViewBlockItem* link_item;
foreach (Block* link, b->linked_clips()) {
if ((link_item = block_items_[link]) != nullptr) {
link_item->setSelected(true);
if (b->type() == Block::kGap) {
continue;
}
if (!new_selected_list.contains(link_item)
&& !rubberband_already_selected_.contains(link_item)) {
new_selected_list.append(link_item);
TrackOutput* t = GetTrackFromReference(block_item->Track());
if (t && t->IsLocked()) {
continue;
}
if (!rubberband_now_selected_.contains(b)) {
AddSelection(block_item);
rubberband_now_selected_.append(b);
}
if (select_links) {
foreach (Block* link, b->linked_clips()) {
if (!rubberband_now_selected_.contains(link)) {
AddSelection(block_items_.value(link));
rubberband_now_selected_.append(link);
}
}
}
}
}
rubberband_now_selected_ = new_selected_list;
}
void TimelineWidget::EndRubberBandSelect()
@@ -1438,14 +1504,57 @@ void TimelineWidget::EndRubberBandSelect()
rubberband_.hide();
// Emit any blocks that were newly selected
QList<Block*> selected_blocks;
foreach (QGraphicsItem* item, rubberband_now_selected_) {
selected_blocks.append(static_cast<TimelineViewBlockItem*>(item)->block());
}
emit BlocksSelected(selected_blocks);
SignalSelectedBlocks(rubberband_now_selected_);
rubberband_now_selected_.clear();
rubberband_already_selected_.clear();
rubberband_old_selections_.clear();
}
void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &track)
{
selections_[track].InsertTimeRange(time);
UpdateViewports(track.type());
}
void TimelineWidget::AddSelection(TimelineViewBlockItem *item)
{
AddSelection(item->block()->range(), item->Track());
}
void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference &track)
{
selections_[track].RemoveTimeRange(time);
UpdateViewports(track.type());
}
void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item)
{
RemoveSelection(item->block()->range(), item->Track());
}
void TimelineWidget::SetSelections(const TimelineWidgetSelections &s)
{
selections_ = s;
UpdateViewports();
}
TimelineViewBlockItem *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord)
{
for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) {
Block* b = it.key();
TimelineViewBlockItem* item = it.value();
if (b->in() <= coord.GetFrame()
&& b->out() > coord.GetFrame()
&& item->Track() == coord.GetTrack()) {
return item;
}
}
return nullptr;
}
struct SnapData {
+115 -364
View File
@@ -34,6 +34,9 @@
#include "widget/nodecopypaste/nodecopypaste.h"
#include "widget/slider/timeslider.h"
#include "widget/timebased/timebased.h"
#include "widget/timelinewidget/timelinewidgetselections.h"
#include "widget/timelinewidget/tool/import.h"
#include "widget/timelinewidget/tool/tool.h"
OLIVE_NAMESPACE_ENTER
@@ -46,13 +49,6 @@ class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget, publi
{
Q_OBJECT
public:
enum DropWithoutSequenceBehavior {
kDWSAsk,
kDWSAuto,
kDWSManual,
kDWSDisable
};
TimelineWidget(QWidget* parent = nullptr);
virtual ~TimelineWidget() override;
@@ -103,6 +99,104 @@ public:
void RestoreSplitterState(const QByteArray& state);
static void ReplaceBlocksWithGaps(const QList<Block *>& blocks, bool remove_from_graph, QUndoCommand* command);
/**
* @brief Retrieve the QGraphicsItem at a particular scene position
*
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord);
const QMap<Block*, TimelineViewBlockItem*>& GetBlockItems() const
{
return block_items_;
}
void AddSelection(const TimeRange& time, const TrackReference& track);
void AddSelection(TimelineViewBlockItem* item);
void RemoveSelection(const TimeRange& time, const TrackReference& track);
void RemoveSelection(TimelineViewBlockItem* item);
const TimelineWidgetSelections& GetSelections() const
{
return selections_;
}
void SetSelections(const TimelineWidgetSelections &s);
TrackOutput* GetTrackFromReference(const TrackReference& ref);
void SetViewBeamCursor(const TimelineCoordinate& coord);
const QVector<TimelineViewGhostItem*>& GetGhostItems() const
{
return ghost_items_;
}
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
void StartRubberBandSelect(bool enable_selecting, bool select_links);
void MoveRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect();
int GetTrackY(const TrackReference& ref);
int GetTrackHeight(const TrackReference& ref);
void AddGhost(TimelineViewGhostItem* ghost);
void ClearGhosts();
bool HasGhosts() const
{
return !ghost_items_.isEmpty();
}
rational GetToolTipTimebase() const;
bool IsBlockSelected(Block* b) const
{
return selected_blocks_.contains(b);
}
void SetBlockLinksSelected(Block *block, bool selected);
void QueueScroll(int value);
TimelineView* GetFirstTimelineView();
const QRect &GetRubberBandGeometry() const;
/**
* @brief Track blocks that have newly been selected (this is preferred over emitting BlocksSelected directly)
*
* TimelineWidget keeps track of which blocks are selected internally. Calling this function will
* add to that list and emit a signal to other widgets that said blocks have been selected.
*
* @param selected_blocks
*
* The list of blocks to add to the internal selection list and signal.
*
* @param filter
*
* TRUE to automatically filter blocks that are already selected from the list. In most cases,
* this is preferable and should only be set to FALSE if the list is guaranteed not to contain
* already selected blocks (and therefore filtering can be skipped to save time).
*/
void SignalSelectedBlocks(QList<Block *> selected_blocks, bool filter = true);
/**
* @brief Track blocks that have been newly deselected
*/
void SignalDeselectedBlocks(const QList<Block*>& deselected_blocks);
/**
* @brief Convenience function to deselect all blocks and signal them
*/
void SignalDeselectedAllBlocks();
signals:
void BlocksSelected(const QList<Block*>& selected_blocks);
@@ -129,341 +223,6 @@ protected:
};
private:
class DraggedFootage {
public:
DraggedFootage(Footage* f, quint64 streams) :
footage_(f),
streams_(streams)
{
}
Footage* footage() const {
return footage_;
}
const quint64& streams() const {
return streams_;
}
private:
Footage* footage_;
quint64 streams_;
};
static DraggedFootage FootageToDraggedFootage(Footage* f);
static QList<DraggedFootage> FootageToDraggedFootage(QList<Footage*> footage);
class Tool
{
public:
Tool(TimelineWidget* parent);
virtual ~Tool();
virtual void MousePress(TimelineViewMouseEvent *){}
virtual void MouseMove(TimelineViewMouseEvent *){}
virtual void MouseRelease(TimelineViewMouseEvent *){}
virtual void MouseDoubleClick(TimelineViewMouseEvent *){}
virtual void HoverMove(TimelineViewMouseEvent *){}
virtual void DragEnter(TimelineViewMouseEvent *){}
virtual void DragMove(TimelineViewMouseEvent *){}
virtual void DragLeave(QDragLeaveEvent *){}
virtual void DragDrop(TimelineViewMouseEvent *){}
TimelineWidget* parent();
static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode);
protected:
/**
* @brief Retrieve the QGraphicsItem at a particular scene position
*
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord);
/**
* @brief Validates Ghosts that are moving horizontally (time-based)
*
* 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 ValidateTimeMovement(rational movement);
/**
* @brief Validates Ghosts that are moving vertically (track-based)
*
* This function's validation ensures that no Ghost's track ends up in a negative (non-existent) track.
*/
int ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem *> &ghosts);
void GetGhostData(rational *earliest_point, rational *latest_point);
void InsertGapsAtGhostDestination(QUndoCommand* command);
QList<rational> snap_points_;
bool dragging_;
TimelineCoordinate drag_start_;
private:
TimelineWidget* parent_;
};
class BeamTool : public Tool
{
public:
BeamTool(TimelineWidget *parent);
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord);
};
class PointerTool : public Tool
{
public:
PointerTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event);
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode);
TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists = false);
TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode);
/**
* @brief Validates Ghosts that are getting their in points trimmed
*
* Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no
* Ghost's length becomes 0 or negative.
*/
rational ValidateInTrimming(rational movement);
/**
* @brief Validates Ghosts that are getting their out points trimmed
*
* Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no
* Ghost's length becomes 0 or negative.
*/
rational ValidateOutTrimming(rational movement);
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos);
void InitiateDragInternal(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode,
bool dont_roll_trims,
bool allow_nongap_rolling, bool slide_instead_of_moving);
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 SetGapTrimmingAllowed(bool e)
{
gap_trimming_allowed_ = e;
}
private:
Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x);
void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode);
bool IsClipTrimmable(TimelineViewBlockItem* clip,
const QList<TimelineViewBlockItem*>& items,
const Timeline::MovementMode& mode);
void ProcessGhostsForSliding();
void ProcessGhostsForRolling();
bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QList<TimelineViewBlockItem *> &selected_items);
bool movement_allowed_;
bool trimming_allowed_;
bool track_movement_allowed_;
bool gap_trimming_allowed_;
bool rubberband_selecting_;
Timeline::TrackType drag_track_type_;
Timeline::MovementMode drag_movement_mode_;
TimelineViewBlockItem* clicked_item_;
};
class ImportTool : public Tool
{
public:
ImportTool(TimelineWidget* parent);
virtual void DragEnter(TimelineViewMouseEvent *event) override;
virtual void DragMove(TimelineViewMouseEvent *event) override;
virtual void DragLeave(QDragLeaveEvent *event) override;
virtual void DragDrop(TimelineViewMouseEvent *event) override;
void PlaceAt(const QList<Footage*> &footage, const rational& start, bool insert);
void PlaceAt(const QList<DraggedFootage> &footage, const rational& start, bool insert);
private:
void FootageToGhosts(rational ghost_start, const QList<DraggedFootage>& footage, const rational &dest_tb, const int &track_start);
void PrepGhosts(const rational &frame, const int &track_index);
void DropGhosts(bool insert);
QList<DraggedFootage> dragged_footage_;
int import_pre_buffer_;
};
class EditTool : public BeamTool
{
public:
EditTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
class RazorTool : public BeamTool
{
public:
RazorTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
QVector<TrackReference> split_tracks_;
};
class RippleTool : public PointerTool
{
public:
RippleTool(TimelineWidget* parent);
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class RollingTool : public PointerTool
{
public:
RollingTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class SlideTool : public PointerTool
{
public:
SlideTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class SlipTool : public PointerTool
{
public:
SlipTool(TimelineWidget* parent);
protected:
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override;
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
};
class ZoomTool : public Tool
{
public:
ZoomTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
class AddTool : public BeamTool
{
public:
AddTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
protected:
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
TimelineViewGhostItem* ghost_;
rational drag_start_point_;
};
class TransitionTool : public AddTool
{
public:
TransitionTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
bool dual_transition_;
};
rational GetToolTipTimebase() const;
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
void ReplaceBlocksWithGaps(const QList<Block *>& blocks, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);
QVector<Timeline::EditToInfo> GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
void RippleTo(Timeline::MovementMode mode);
@@ -472,54 +231,44 @@ private:
void ShowSnap(const QList<rational>& times);
void UpdateViewports(const Timeline::TrackType& type = Timeline::kTrackTypeNone);
QPoint drag_origin_;
void StartRubberBandSelect(bool enable_selecting, bool select_links);
void MoveRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect();
QRubberBand rubberband_;
QList<QGraphicsItem*> rubberband_already_selected_;
QList<QGraphicsItem*> rubberband_now_selected_;
TimelineWidgetSelections rubberband_old_selections_;
QList<Block*> rubberband_now_selected_;
Tool* GetActiveTool();
TimelineWidgetSelections selections_;
QVector<Tool*> tools_;
TimelineTool* GetActiveTool();
QVector<TimelineTool*> tools_;
ImportTool* import_tool_;
Tool* active_tool_;
void ClearGhosts();
bool HasGhosts();
TimelineTool* active_tool_;
QVector<TimelineViewGhostItem*> ghost_items_;
QMap<Block*, TimelineViewBlockItem*> block_items_;
TrackOutput* GetTrackFromReference(const TrackReference& ref);
QList<TimelineAndTrackView*> views_;
TimeSlider* timecode_label_;
QList<Block*> selected_blocks_;
int deferred_scroll_value_;
bool use_audio_time_units_;
QSplitter* view_splitter_;
int GetTrackY(const TrackReference& ref);
int GetTrackHeight(const TrackReference& ref);
void CenterOn(qreal scene_pos);
void AddGhost(TimelineViewGhostItem* ghost);
void UpdateViewTimebases();
void SetViewBeamCursor(const TimelineCoordinate& coord);
private slots:
void ViewMousePressed(TimelineViewMouseEvent* event);
void ViewMouseMoved(TimelineViewMouseEvent* event);
@@ -532,7 +281,7 @@ private slots:
void ViewDragDropped(TimelineViewMouseEvent* event);
void AddBlock(Block* block, TrackReference track);
void RemoveBlock(Block* block);
void RemoveBlock(const QList<Block*>& blocks);
void AddTrack(TrackOutput* track, Timeline::TrackType type);
void RemoveTrack(TrackOutput* track);
@@ -568,6 +317,8 @@ private slots:
void ViewTimestampChanged(int64_t ts);
void ToolChanged();
};
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,77 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "timelinewidgetselections.h"
OLIVE_NAMESPACE_ENTER
void TimelineWidgetSelections::ShiftTime(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2) += diff;
}
}
}
void TimelineWidgetSelections::ShiftTracks(Timeline::TrackType type, int diff)
{
TimelineWidgetSelections cached_selections;
{
// Take all selections of this track type
auto it = this->begin();
while (it != this->end()) {
if (it.key().type() == type) {
cached_selections.insert(it.key(), it.value());
it = this->erase(it);
} else {
it++;
}
}
}
// Then re-insert them with the diff applied
for (auto it=cached_selections.cbegin(); it!=cached_selections.cend(); it++) {
TrackReference ref(it.key().type(), it.key().index() + diff);
this->insert(ref, it.value());
}
}
void TimelineWidgetSelections::TrimIn(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2).set_in((*it2).in() + diff);
}
}
}
void TimelineWidgetSelections::TrimOut(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2).set_out((*it2).out() + diff);
}
}
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,48 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef TIMELINEWIDGETSELECTIONS_H
#define TIMELINEWIDGETSELECTIONS_H
#include <QHash>
#include "common/timerange.h"
#include "timeline/trackreference.h"
OLIVE_NAMESPACE_ENTER
class TimelineWidgetSelections : public QHash<TrackReference, TimeRangeList>
{
public:
TimelineWidgetSelections() = default;
void ShiftTime(const rational& diff);
void ShiftTracks(Timeline::TrackType type, int diff);
void TrimIn(const rational& diff);
void TrimOut(const rational& diff);
};
OLIVE_NAMESPACE_EXIT
#endif // TIMELINEWIDGETSELECTIONS_H
@@ -17,17 +17,30 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timelinewidget/tool/add.cpp
widget/timelinewidget/tool/add.h
widget/timelinewidget/tool/beam.cpp
widget/timelinewidget/tool/beam.h
widget/timelinewidget/tool/edit.cpp
widget/timelinewidget/tool/edit.h
widget/timelinewidget/tool/import.cpp
widget/timelinewidget/tool/import.h
widget/timelinewidget/tool/pointer.cpp
widget/timelinewidget/tool/pointer.h
widget/timelinewidget/tool/razor.cpp
widget/timelinewidget/tool/razor.h
widget/timelinewidget/tool/ripple.cpp
widget/timelinewidget/tool/ripple.h
widget/timelinewidget/tool/rolling.cpp
widget/timelinewidget/tool/rolling.h
widget/timelinewidget/tool/slide.cpp
widget/timelinewidget/tool/slide.h
widget/timelinewidget/tool/slip.cpp
widget/timelinewidget/tool/slip.h
widget/timelinewidget/tool/transition.cpp
widget/timelinewidget/tool/transition.h
widget/timelinewidget/tool/tool.cpp
widget/timelinewidget/tool/tool.h
widget/timelinewidget/tool/zoom.cpp
widget/timelinewidget/tool/zoom.h
PARENT_SCOPE
)

Some files were not shown because too many files have changed in this diff Show More