From 8b35c22c8397c0d26d9eec49f05d9f88ed53aa3e Mon Sep 17 00:00:00 2001 From: Bennett Anderson Date: Fri, 22 Apr 2022 15:41:45 -0700 Subject: [PATCH 01/62] Add sanitizer options to build system --- CMakeLists.txt | 6 +++++ cmake/Sanitizers.cmake | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 cmake/Sanitizers.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index acc43c97a..2aa89ac6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,12 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) +# Sanitizers +add_library(olive-sanitizers INTERFACE) +include(cmake/Sanitizers.cmake) +enable_sanitizers(olive-sanitizers) +list(APPEND OLIVE_LIBRARIES olive-sanitizers) + # Set compiler options if(MSVC) set(OLIVE_COMPILE_OPTIONS diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 000000000..d4a16fd17 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,51 @@ +function(enable_sanitizers project_name) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(SANITIZERS "") + + option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) + if(ENABLE_SANITIZER_ADDRESS) + list(APPEND SANITIZERS "address") + endif() + + option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + if(ENABLE_SANITIZER_LEAK) + list(APPEND SANITIZERS "leak") + endif() + + option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" OFF) + if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + list(APPEND SANITIZERS "undefined") + endif() + + option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + if(ENABLE_SANITIZER_THREAD) + if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "thread") + endif() + endif() + + option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) + if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + message(WARNING "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives") + if("address" IN_LIST SANITIZERS + OR "thread" IN_LIST SANITIZERS + OR "leak" IN_LIST SANITIZERS) + message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "memory") + endif() + endif() + + list(JOIN SANITIZERS "," LIST_OF_SANITIZERS) + endif() + + if(LIST_OF_SANITIZERS) + if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") + target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + endif() + endif() + +endfunction() From b11e681d2dba3a82f5a72bdcd0e2d4ea20b70264 Mon Sep 17 00:00:00 2001 From: Bennett Date: Mon, 2 May 2022 16:33:56 -0700 Subject: [PATCH 02/62] Add support for using sanitizer options with MSVC --- cmake/Sanitizers.cmake | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake index d4a16fd17..f6d658cf0 100644 --- a/cmake/Sanitizers.cmake +++ b/cmake/Sanitizers.cmake @@ -1,5 +1,5 @@ function(enable_sanitizers project_name) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(SANITIZERS "") option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) @@ -27,7 +27,7 @@ function(enable_sanitizers project_name) endif() option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) - if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") message(WARNING "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives") if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS @@ -42,9 +42,16 @@ function(enable_sanitizers project_name) endif() if(LIST_OF_SANITIZERS) + set(SANITIZE_PREFIX "") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(SANITIZE_PREFIX "/fsanitize") + else() + set(SANITIZE_PREFIX "-fsanitize") + endif() + if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") - target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) - target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + target_compile_options(${project_name} INTERFACE ${SANITIZE_PREFIX}=${LIST_OF_SANITIZERS}) + target_link_options(${project_name} INTERFACE ${SANITIZE_PREFIX}=${LIST_OF_SANITIZERS}) endif() endif() From 11ea7fcb71a5b61fa4918a0977da8eb86c0b6380 Mon Sep 17 00:00:00 2001 From: Bennett Date: Tue, 3 May 2022 17:57:22 -0700 Subject: [PATCH 03/62] Initial threadpool rewrite/cleanup --- app/render/rendermanager.cpp | 38 +++----- app/threading/threadpool.cpp | 175 +++++++++++------------------------ app/threading/threadpool.h | 81 ++++++---------- 3 files changed, 91 insertions(+), 203 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index ac5d297de..a20033217 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -40,8 +40,7 @@ RenderManager* RenderManager::instance_ = nullptr; const int RenderManager::kDecoderMaximumInactivity = 10000; RenderManager::RenderManager(QObject *parent) : - ThreadPool(QThread::IdlePriority, 0, parent), - backend_(kOpenGL) + ThreadPool(0, parent), backend_(kOpenGL) { Renderer* graphics_renderer = nullptr; @@ -160,14 +159,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* c ticket->setProperty("cache", cache->GetCacheDirectory()); } - if (ticket->thread() != this->thread()) { - ticket->moveToThread(this->thread()); - } - - // Queue appending the ticket and running the next job on our thread to make this function thread-safe - QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, - OLIVE_NS_ARG(RenderTicketPtr, ticket), - Q_ARG(bool, prioritize)); + AddTicket(ticket); return ticket; } @@ -189,14 +181,7 @@ RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange ticket->setProperty("enablewaveforms", generate_waveforms); ticket->setProperty("aparam", QVariant::fromValue(params)); - if (ticket->thread() != this->thread()) { - ticket->moveToThread(this->thread()); - } - - // Queue appending the ticket and running the next job on our thread to make this function thread-safe - QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, - OLIVE_NS_ARG(RenderTicketPtr, ticket), - Q_ARG(bool, prioritize)); + AddTicket(ticket); return ticket; } @@ -211,20 +196,21 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr ticket->setProperty("hash", hash); ticket->setProperty("type", kTypeVideoDownload); - if (ticket->thread() != this->thread()) { - ticket->moveToThread(this->thread()); - } - - // Queue appending the ticket and running the next job on our thread to make this function thread-safe - QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, - OLIVE_NS_ARG(RenderTicketPtr, ticket), - Q_ARG(bool, prioritize)); + AddTicket(ticket); return ticket; } void RenderManager::RunTicket(RenderTicketPtr ticket) const { + // Setup the ticket for ::Process + ticket->Start(); + + if (ticket->IsCancelled()) { + ticket->Finish(); + return; + } + RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_, default_shader_); } diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index cef8500c0..96a5f1106 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -22,140 +22,71 @@ namespace olive { -ThreadPool::ThreadPool(QThread::Priority priority, int threads, QObject *parent) : - QObject(parent) +ThreadPool::ThreadPool(unsigned threads, QObject *parent) + : QObject(parent) { - all_threads_.resize(threads ? threads : QThread::idealThreadCount()); + if (threads == 0) { + threads = std::thread::hardware_concurrency(); + } - // Create threads - for (int i=0; i lock(task_mutex_); - // Append to list of available threads - available_threads_.push_back(t); + if (priority == RenderTicketPriority::kHigh) { + tasks_.emplace_front(std::move(ticket)); + } else { + tasks_.emplace_back(std::move(ticket)); + } - // Connect done signal - connect(t, &ThreadPoolThread::Done, this, &ThreadPool::ThreadDone); + cond_.notify_one(); +} - // Start the thread at the given priority - t->start(priority); +bool ThreadPool::RemoveTicket(RenderTicketPtr ticket) +{ + std::lock_guard lock(task_mutex_); + + const auto it = std::find(tasks_.begin(), tasks_.end(), ticket); + if (it == tasks_.end()) { + return false; + } + + tasks_.erase(it); + return true; +} + +void ThreadPool::thread_exec() { + while (true) { + TaskType task; + + { + std::unique_lock lock(task_mutex_); + cond_.wait(lock, [this]{ return this->end_threadp_ || !this->tasks_.empty(); }); + + if (this->end_threadp_ && this->tasks_.empty()) { + break; + } + + task = std::move(tasks_.front()); + tasks_.pop_front(); + } + + RunTicket(task); } } ThreadPool::~ThreadPool() { - foreach (ThreadPoolThread* thread, all_threads_) { - thread->Cancel(); - thread->wait(); - delete thread; + end_threadp_ = true; + cond_.notify_all(); + + for (auto &e : worker_threads_) { + e.join(); } } -bool ThreadPool::RemoveTicket(RenderTicketPtr ticket) -{ - auto it = std::find(ticket_queue_.begin(), ticket_queue_.end(), ticket); - if (it == ticket_queue_.end()) { - return false; - } - - ticket_queue_.erase(it); - return true; -} - -void ThreadPool::AddTicket(RenderTicketPtr ticket, bool prioritize) -{ - if (prioritize) { - ticket_queue_.push_front(ticket); - } else { - ticket_queue_.push_back(ticket); - } - - RunNext(); -} - -void ThreadPool::RunNext() -{ - while (!ticket_queue_.empty() && !available_threads_.empty()) { - // Run function - RenderTicketPtr ticket = ticket_queue_.front(); - ticket_queue_.pop_front(); - - ticket->Start(); - - if (ticket->IsCancelled()) { - // Finish without doing any more - ticket->Finish(); - } else { - ThreadPoolThread* thread = available_threads_.front(); - available_threads_.pop_front(); - - // Move ticket to other thread so event processing can occur there - ticket->moveToThread(thread); - - // Run the ticket in the thread, which actually just calls our virtual function RunTicket - thread->RunTicket(ticket); - } - } -} - -void ThreadPool::ThreadDone() -{ - ThreadPoolThread* thread = static_cast(sender()); - - available_threads_.push_back(thread); - - RunNext(); -} - -ThreadPoolThread::ThreadPoolThread(ThreadPool *parent) -{ - pool_ = parent; - - // Ensures mutex is definitely locked by the time the thread is running - mutex_.lock(); -} - -ThreadPoolThread::~ThreadPoolThread() -{ - mutex_.unlock(); -} - -void ThreadPoolThread::RunTicket(RenderTicketPtr ticket) -{ - mutex_.lock(); - ticket_ = ticket; - wait_cond_.wakeAll(); - mutex_.unlock(); -} - -void ThreadPoolThread::run() -{ - while (true) { - wait_cond_.wait(&mutex_); - - if (ticket_) { - pool_->RunTicket(ticket_); - - // Move back to calling thread (hacky?) - ticket_->moveToThread(this->thread()); - - ticket_ = nullptr; - } - - if (IsCancelled()) { - break; - } else { - emit Done(); - } - } -} - -void ThreadPoolThread::CancelEvent() -{ - wait_cond_.wakeAll(); -} - } diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index ee1e29b61..e56fef52a 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -21,75 +21,46 @@ #ifndef THREADPOOL_H #define THREADPOOL_H -#include - -#include "common/cancelableobject.h" #include "threading/threadticket.h" +#include +#include +#include +#include +#include + namespace olive { -class ThreadPoolThread; +enum class RenderTicketPriority { kHigh = 0, kNormal }; class ThreadPool : public QObject { Q_OBJECT public: - ThreadPool(QThread::Priority priority = QThread::InheritPriority, int threads = 0, QObject* parent = nullptr); + using TaskType = RenderTicketPtr; + ThreadPool(unsigned threads, QObject *parent); + + ThreadPool(const ThreadPool &) = delete; + ThreadPool(ThreadPool &&) = delete; + ThreadPool & operator=(const ThreadPool&) = delete; + ThreadPool & operator=(ThreadPool &&) = delete; + + virtual void RunTicket(RenderTicketPtr ticket) const = 0; + void AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority = RenderTicketPriority::kNormal); + bool RemoveTicket(RenderTicketPtr ticket); virtual ~ThreadPool() override; - RenderTicketPtr Queue(); - - virtual void RunTicket(RenderTicketPtr ticket) const = 0; - - bool RemoveTicket(RenderTicketPtr ticket); - -public slots: - void AddTicket(olive::RenderTicketPtr ticket, bool prioritize = false); - private: - void RunNext(); - - QVector all_threads_; - - std::list available_threads_; - - std::list ticket_queue_; - -private slots: - void ThreadDone(); + void thread_exec(); + std::vector worker_threads_; + std::deque tasks_; + std::mutex task_mutex_; + std::condition_variable cond_; + std::atomic_bool end_threadp_{false}; }; -class ThreadPoolThread : public QThread, public CancelableObject -{ - Q_OBJECT -public: - ThreadPoolThread(ThreadPool* parent); +} // namespace olive - virtual ~ThreadPoolThread() override; - - void RunTicket(RenderTicketPtr ticket); - -protected: - virtual void run() override; - - virtual void CancelEvent() override; - -signals: - void Done(); - -private: - ThreadPool* pool_; - - RenderTicketPtr ticket_; - - QMutex mutex_; - - QWaitCondition wait_cond_; - -}; - -} - -#endif // THREADPOOL_H +#endif // THREADPOOL_H From f9db19e91c6f7d58f136fe7d70fe2eab986eb9e6 Mon Sep 17 00:00:00 2001 From: Bennett Date: Tue, 3 May 2022 18:00:28 -0700 Subject: [PATCH 04/62] Always prefer constexpr when possible --- app/render/rendermanager.cpp | 2 -- app/render/rendermanager.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index a20033217..3894c9003 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -35,9 +35,7 @@ #include "window/mainwindow/mainwindow.h" namespace olive { - RenderManager* RenderManager::instance_ = nullptr; -const int RenderManager::kDecoderMaximumInactivity = 10000; RenderManager::RenderManager(QObject *parent) : ThreadPool(0, parent), backend_(kOpenGL) diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index df8c00d29..42ad4f8a9 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -144,7 +144,7 @@ private: QTimer decoder_clear_timer_; - static const int kDecoderMaximumInactivity; + static constexpr auto kDecoderMaximumInactivity = 10000; private slots: void ClearOldDecoders(); From 31dc557909276a4a2cef697912c4b86f1893d8c9 Mon Sep 17 00:00:00 2001 From: Bennett Anderson Date: Wed, 4 May 2022 09:47:17 -0700 Subject: [PATCH 05/62] Update copyright year --- app/render/rendermanager.cpp | 2 +- app/render/rendermanager.h | 2 +- app/threading/threadpool.cpp | 2 +- app/threading/threadpool.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 3894c9003..43acad8c3 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 42ad4f8a9..39b52fc9f 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index 96a5f1106..017d811ba 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index e56fef52a..0be4b30ef 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 From 19dc803020839321f8ec8013bbea8d89fc706d6e Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 4 May 2022 19:53:53 -0400 Subject: [PATCH 06/62] Fix misc. source comment typos --- app/audio/audiovisualwaveform.cpp | 2 +- app/codec/ffmpeg/ffmpegencoder.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index dbc0cb973..290adb0f5 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -287,7 +287,7 @@ void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float __m128 min = _mm_loadu_ps(a + start); // loop over 'a' and compare current elements with min and max 4 by 4. - // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 + // we need to make sure we don't read out of boundaries should 'a' length be not mod. 4 for(int i = 4; i < length-4; i+=4) { __m128 cur = _mm_loadu_ps(a + start + i); max = _mm_max_ps(max, cur); diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 19db89732..599e4c9de 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -69,7 +69,7 @@ std::vector FFmpegEncoder::GetSampleFormatsForCodec(ExportC if (c == ExportCodec::kCodecPCM) { // FFmpeg lists these as separate codecs so we need custom functionality here // We list signed 16 first because ExportDialog will always use the first element by default - // (beacuse first element is the "default" in FFmpeg) + // (because first element is the "default" in FFmpeg) f = { AudioParams::kFormatSigned16Packed, AudioParams::kFormatUnsigned8Packed, From 4acb89ab1e56977d6190e3678725183964bd1042 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 5 May 2022 09:35:35 -0700 Subject: [PATCH 07/62] minor formatting Yes, we will get a proper clang-format at some point --- app/render/rendermanager.cpp | 4 +++- app/threading/threadpool.cpp | 6 ++++-- app/threading/threadpool.h | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 43acad8c3..e779820ff 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -35,10 +35,12 @@ #include "window/mainwindow/mainwindow.h" namespace olive { + RenderManager* RenderManager::instance_ = nullptr; RenderManager::RenderManager(QObject *parent) : - ThreadPool(0, parent), backend_(kOpenGL) + ThreadPool(0, parent), + backend_(kOpenGL) { Renderer* graphics_renderer = nullptr; diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index 017d811ba..24a90bf1d 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -34,7 +34,8 @@ ThreadPool::ThreadPool(unsigned threads, QObject *parent) } } -void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority) { +void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority) +{ std::lock_guard lock(task_mutex_); if (priority == RenderTicketPriority::kHigh) { @@ -59,7 +60,8 @@ bool ThreadPool::RemoveTicket(RenderTicketPtr ticket) return true; } -void ThreadPool::thread_exec() { +void ThreadPool::thread_exec() +{ while (true) { TaskType task; diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index 0be4b30ef..7a7ea12df 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -59,6 +59,7 @@ private: std::mutex task_mutex_; std::condition_variable cond_; std::atomic_bool end_threadp_{false}; + }; } // namespace olive From e2db72860db4b140af527d89d694f1e459260b1b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 9 May 2022 20:21:05 -0700 Subject: [PATCH 08/62] core: remove unused functions --- app/core.cpp | 19 ------------------- app/core.h | 3 --- 2 files changed, 22 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 0010cae4d..a465d8add 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1412,25 +1412,6 @@ int Core::CountFilesInFileList(const QFileInfoList &filenames) return file_count; } -QString GetRenderModePreferencePrefix(RenderMode::Mode mode, const QString &preference) { - QString key; - - key.append((mode == RenderMode::kOffline) ? QStringLiteral("Offline") : QStringLiteral("Online")); - key.append(preference); - - return key; -} - -QVariant Core::GetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference) -{ - return OLIVE_CONFIG_STR(GetRenderModePreferencePrefix(mode, preference)); -} - -void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference, const QVariant &value) -{ - OLIVE_CONFIG_STR(GetRenderModePreferencePrefix(mode, preference)) = value; -} - bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { diff --git a/app/core.h b/app/core.h index 759cdd522..8466fa435 100644 --- a/app/core.h +++ b/app/core.h @@ -247,9 +247,6 @@ public: */ static int CountFilesInFileList(const QFileInfoList &filenames); - static QVariant GetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference); - static void SetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference, const QVariant& value); - /** * @brief Show a dialog to the user to rename a set of nodes */ From 158c1fecb64aab86bef0ae3631019d015d2dff78 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 09:08:49 -0700 Subject: [PATCH 09/62] import: fixed crash when importing folders --- app/task/project/import/import.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 900cc1d8b..150fcaedf 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -222,7 +222,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command) { // Create undoable command that adds the items to the model - Project* project = folder->project(); + Project* project = folder_->project(); NodeAddCommand* nac = new NodeAddCommand(project, item); nac->PushToThread(project->thread()); From 4b366a9b732f78bee34c428672fe63b730722d37 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 10:38:56 -0700 Subject: [PATCH 10/62] update copyright year to 2022 --- CMakeLists.txt | 2 +- app/CMakeLists.txt | 2 +- app/audio/CMakeLists.txt | 2 +- app/audio/audiomanager.cpp | 2 +- app/audio/audiomanager.h | 2 +- app/audio/audioprocessor.cpp | 2 +- app/audio/audioprocessor.h | 2 +- app/audio/audiovisualwaveform.cpp | 2 +- app/audio/audiovisualwaveform.h | 2 +- app/cli/CMakeLists.txt | 2 +- app/cli/cliexport/cliexportmanager.cpp | 2 +- app/cli/cliexport/cliexportmanager.h | 2 +- app/cli/cliprogress/CMakeLists.txt | 2 +- app/cli/cliprogress/cliprogressdialog.cpp | 2 +- app/cli/cliprogress/cliprogressdialog.h | 2 +- app/cli/clitask/CMakeLists.txt | 2 +- app/cli/clitask/clitaskdialog.cpp | 2 +- app/cli/clitask/clitaskdialog.h | 2 +- app/codec/CMakeLists.txt | 2 +- app/codec/decoder.cpp | 2 +- app/codec/decoder.h | 2 +- app/codec/encoder.cpp | 2 +- app/codec/encoder.h | 2 +- app/codec/exportcodec.cpp | 2 +- app/codec/exportcodec.h | 2 +- app/codec/exportformat.cpp | 2 +- app/codec/exportformat.h | 2 +- app/codec/ffmpeg/CMakeLists.txt | 2 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 2 +- app/codec/ffmpeg/ffmpegdecoder.h | 2 +- app/codec/ffmpeg/ffmpegencoder.cpp | 2 +- app/codec/ffmpeg/ffmpegencoder.h | 2 +- app/codec/ffmpeg/ffmpegframepool.cpp | 2 +- app/codec/ffmpeg/ffmpegframepool.h | 2 +- app/codec/frame.cpp | 2 +- app/codec/frame.h | 2 +- app/codec/oiio/CMakeLists.txt | 2 +- app/codec/oiio/oiiodecoder.cpp | 2 +- app/codec/oiio/oiiodecoder.h | 2 +- app/codec/oiio/oiioencoder.cpp | 2 +- app/codec/oiio/oiioencoder.h | 2 +- app/codec/planarfiledevice.cpp | 2 +- app/codec/planarfiledevice.h | 2 +- app/codec/samplebuffer.cpp | 2 +- app/codec/samplebuffer.h | 2 +- app/common/CMakeLists.txt | 2 +- app/common/autoscroll.h | 2 +- app/common/bezier.cpp | 2 +- app/common/bezier.h | 2 +- app/common/cancelableobject.h | 2 +- app/common/channellayout.h | 2 +- app/common/clamp.h | 2 +- app/common/commandlineparser.cpp | 4 ++-- app/common/commandlineparser.h | 2 +- app/common/cpuoptimize.h | 2 +- app/common/crashpadinterface.cpp | 2 +- app/common/crashpadinterface.h | 2 +- app/common/crashpadutils.h | 2 +- app/common/debug.cpp | 2 +- app/common/debug.h | 2 +- app/common/decibel.h | 2 +- app/common/define.h | 2 +- app/common/digit.h | 2 +- app/common/ffmpegutils.cpp | 2 +- app/common/ffmpegutils.h | 2 +- app/common/filefunctions.cpp | 2 +- app/common/filefunctions.h | 2 +- app/common/flipmodifiers.cpp | 2 +- app/common/flipmodifiers.h | 2 +- app/common/functiontimer.h | 2 +- app/common/lerp.h | 2 +- app/common/memorypool.h | 2 +- app/common/ocioutils.cpp | 2 +- app/common/ocioutils.h | 2 +- app/common/oiioutils.cpp | 2 +- app/common/oiioutils.h | 2 +- app/common/power.h | 2 +- app/common/qtutils.cpp | 2 +- app/common/qtutils.h | 2 +- app/common/range.h | 2 +- app/common/ratiodialog.cpp | 2 +- app/common/ratiodialog.h | 2 +- app/common/rational.cpp | 2 +- app/common/rational.h | 2 +- app/common/threadedobject.cpp | 2 +- app/common/threadedobject.h | 2 +- app/common/timecodefunctions.cpp | 2 +- app/common/timecodefunctions.h | 2 +- app/common/timerange.cpp | 2 +- app/common/timerange.h | 2 +- app/common/util.h | 2 +- app/common/xmlutils.cpp | 2 +- app/common/xmlutils.h | 2 +- app/config/CMakeLists.txt | 2 +- app/config/config.cpp | 2 +- app/config/config.h | 2 +- app/core.cpp | 2 +- app/core.h | 2 +- app/crashhandler/CMakeLists.txt | 2 +- app/crashhandler/crashhandler.cpp | 2 +- app/crashhandler/crashhandler.h | 2 +- app/dialog/CMakeLists.txt | 2 +- app/dialog/about/CMakeLists.txt | 2 +- app/dialog/about/about.cpp | 2 +- app/dialog/about/about.h | 2 +- app/dialog/about/scrollinglabel.cpp | 2 +- app/dialog/about/scrollinglabel.h | 2 +- app/dialog/actionsearch/CMakeLists.txt | 2 +- app/dialog/autorecovery/CMakeLists.txt | 2 +- app/dialog/autorecovery/autorecoverydialog.cpp | 2 +- app/dialog/autorecovery/autorecoverydialog.h | 2 +- app/dialog/color/CMakeLists.txt | 2 +- app/dialog/color/colordialog.cpp | 2 +- app/dialog/color/colordialog.h | 2 +- app/dialog/configbase/CMakeLists.txt | 2 +- app/dialog/configbase/configdialogbase.cpp | 2 +- app/dialog/configbase/configdialogbase.h | 2 +- app/dialog/configbase/configdialogbasetab.cpp | 2 +- app/dialog/configbase/configdialogbasetab.h | 2 +- app/dialog/diskcache/CMakeLists.txt | 2 +- app/dialog/diskcache/diskcachedialog.cpp | 2 +- app/dialog/diskcache/diskcachedialog.h | 2 +- app/dialog/export/CMakeLists.txt | 2 +- app/dialog/export/codec/CMakeLists.txt | 2 +- app/dialog/export/codec/cineformsection.cpp | 2 +- app/dialog/export/codec/cineformsection.h | 2 +- app/dialog/export/codec/codecsection.cpp | 2 +- app/dialog/export/codec/codecsection.h | 2 +- app/dialog/export/codec/codecstack.cpp | 2 +- app/dialog/export/codec/codecstack.h | 2 +- app/dialog/export/codec/h264section.cpp | 2 +- app/dialog/export/codec/h264section.h | 2 +- app/dialog/export/codec/imagesection.cpp | 2 +- app/dialog/export/codec/imagesection.h | 2 +- app/dialog/export/export.cpp | 2 +- app/dialog/export/export.h | 2 +- app/dialog/export/exportaudiotab.cpp | 2 +- app/dialog/export/exportaudiotab.h | 2 +- app/dialog/export/exportformatcombobox.cpp | 2 +- app/dialog/export/exportformatcombobox.h | 2 +- app/dialog/export/exportsubtitlestab.h | 2 +- app/dialog/export/exportvideotab.cpp | 2 +- app/dialog/export/exportvideotab.h | 2 +- app/dialog/footagerelink/CMakeLists.txt | 2 +- app/dialog/keyframeproperties/CMakeLists.txt | 2 +- app/dialog/keyframeproperties/keyframeproperties.cpp | 2 +- app/dialog/keyframeproperties/keyframeproperties.h | 2 +- app/dialog/markerproperties/CMakeLists.txt | 2 +- app/dialog/markerproperties/markerpropertiesdialog.cpp | 2 +- app/dialog/markerproperties/markerpropertiesdialog.h | 2 +- app/dialog/preferences/CMakeLists.txt | 2 +- app/dialog/preferences/keysequenceeditor.cpp | 2 +- app/dialog/preferences/keysequenceeditor.h | 2 +- app/dialog/preferences/preferences.cpp | 2 +- app/dialog/preferences/preferences.h | 2 +- app/dialog/preferences/tabs/CMakeLists.txt | 2 +- app/dialog/preferences/tabs/preferencesappearancetab.cpp | 2 +- app/dialog/preferences/tabs/preferencesappearancetab.h | 2 +- app/dialog/preferences/tabs/preferencesaudiotab.cpp | 2 +- app/dialog/preferences/tabs/preferencesaudiotab.h | 2 +- app/dialog/preferences/tabs/preferencesbehaviortab.cpp | 2 +- app/dialog/preferences/tabs/preferencesbehaviortab.h | 2 +- app/dialog/preferences/tabs/preferencesdisktab.cpp | 2 +- app/dialog/preferences/tabs/preferencesdisktab.h | 2 +- app/dialog/preferences/tabs/preferencesgeneraltab.cpp | 2 +- app/dialog/preferences/tabs/preferencesgeneraltab.h | 2 +- app/dialog/preferences/tabs/preferenceskeyboardtab.cpp | 2 +- app/dialog/preferences/tabs/preferenceskeyboardtab.h | 2 +- app/dialog/progress/CMakeLists.txt | 2 +- app/dialog/progress/progress.cpp | 2 +- app/dialog/progress/progress.h | 2 +- app/dialog/rendercancel/CMakeLists.txt | 2 +- app/dialog/rendercancel/rendercancel.cpp | 2 +- app/dialog/rendercancel/rendercancel.h | 2 +- app/dialog/sequence/CMakeLists.txt | 2 +- app/dialog/sequence/presetmanager.h | 2 +- app/dialog/sequence/sequence.cpp | 2 +- app/dialog/sequence/sequence.h | 2 +- app/dialog/sequence/sequencedialogpresettab.cpp | 2 +- app/dialog/sequence/sequencedialogpresettab.h | 2 +- app/dialog/sequence/sequencepreset.h | 2 +- app/dialog/speedduration/CMakeLists.txt | 2 +- app/dialog/speedduration/speeddurationdialog.cpp | 2 +- app/dialog/speedduration/speeddurationdialog.h | 2 +- app/dialog/task/CMakeLists.txt | 2 +- app/dialog/task/task.cpp | 2 +- app/dialog/task/task.h | 2 +- app/dialog/text/CMakeLists.txt | 2 +- app/dialog/text/text.cpp | 2 +- app/dialog/text/text.h | 2 +- app/main.cpp | 2 +- app/node/CMakeLists.txt | 2 +- app/node/audio/CMakeLists.txt | 2 +- app/node/audio/pan/CMakeLists.txt | 2 +- app/node/audio/pan/pan.cpp | 2 +- app/node/audio/pan/pan.h | 2 +- app/node/audio/volume/CMakeLists.txt | 2 +- app/node/audio/volume/volume.cpp | 2 +- app/node/audio/volume/volume.h | 2 +- app/node/block/CMakeLists.txt | 2 +- app/node/block/block.cpp | 2 +- app/node/block/block.h | 2 +- app/node/block/clip/CMakeLists.txt | 2 +- app/node/block/clip/clip.cpp | 2 +- app/node/block/clip/clip.h | 2 +- app/node/block/gap/CMakeLists.txt | 2 +- app/node/block/gap/gap.cpp | 2 +- app/node/block/gap/gap.h | 2 +- app/node/block/subtitle/CMakeLists.txt | 2 +- app/node/block/subtitle/subtitle.cpp | 2 +- app/node/block/subtitle/subtitle.h | 2 +- app/node/block/transition/CMakeLists.txt | 2 +- app/node/block/transition/crossdissolve/CMakeLists.txt | 2 +- .../transition/crossdissolve/crossdissolvetransition.cpp | 2 +- .../transition/crossdissolve/crossdissolvetransition.h | 2 +- app/node/block/transition/diptocolor/CMakeLists.txt | 2 +- .../block/transition/diptocolor/diptocolortransition.cpp | 2 +- .../block/transition/diptocolor/diptocolortransition.h | 2 +- app/node/block/transition/transition.cpp | 2 +- app/node/block/transition/transition.h | 2 +- app/node/color/CMakeLists.txt | 2 +- app/node/color/colormanager/CMakeLists.txt | 2 +- app/node/color/colormanager/colormanager.cpp | 2 +- app/node/color/colormanager/colormanager.h | 2 +- app/node/color/displaytransform/CMakeLists.txt | 2 +- app/node/color/displaytransform/displaytransform.cpp | 2 +- app/node/color/displaytransform/displaytransform.h | 2 +- app/node/color/ociobase/CMakeLists.txt | 2 +- app/node/color/ociobase/ociobase.cpp | 2 +- app/node/color/ociobase/ociobase.h | 2 +- app/node/color/ociogradingtransformlinear/CMakeLists.txt | 2 +- .../ociogradingtransformlinear.cpp | 2 +- .../ociogradingtransformlinear.h | 2 +- app/node/distort/CMakeLists.txt | 2 +- app/node/distort/cornerpin/CMakeLists.txt | 2 +- app/node/distort/cornerpin/cornerpindistortnode.cpp | 2 +- app/node/distort/cornerpin/cornerpindistortnode.h | 2 +- app/node/distort/crop/CMakeLists.txt | 2 +- app/node/distort/crop/cropdistortnode.cpp | 2 +- app/node/distort/crop/cropdistortnode.h | 2 +- app/node/distort/flip/CMakeLists.txt | 2 +- app/node/distort/flip/flipdistortnode.cpp | 2 +- app/node/distort/flip/flipdistortnode.h | 2 +- app/node/distort/mask/CMakeLists.txt | 2 +- app/node/distort/mask/mask.cpp | 2 +- app/node/distort/mask/mask.h | 2 +- app/node/distort/transform/CMakeLists.txt | 2 +- app/node/distort/transform/transformdistortnode.cpp | 2 +- app/node/distort/transform/transformdistortnode.h | 2 +- app/node/effect/CMakeLists.txt | 2 +- app/node/effect/opacity/CMakeLists.txt | 2 +- app/node/factory.cpp | 2 +- app/node/factory.h | 2 +- app/node/filter/CMakeLists.txt | 2 +- app/node/filter/blur/CMakeLists.txt | 2 +- app/node/filter/blur/blur.cpp | 2 +- app/node/filter/blur/blur.h | 2 +- app/node/filter/mosaic/CMakeLists.txt | 2 +- app/node/filter/mosaic/mosaicfilternode.cpp | 2 +- app/node/filter/mosaic/mosaicfilternode.h | 2 +- app/node/filter/stroke/CMakeLists.txt | 2 +- app/node/filter/stroke/stroke.cpp | 2 +- app/node/filter/stroke/stroke.h | 2 +- app/node/generator/CMakeLists.txt | 2 +- app/node/generator/matrix/CMakeLists.txt | 2 +- app/node/generator/matrix/matrix.cpp | 2 +- app/node/generator/matrix/matrix.h | 2 +- app/node/generator/noise/CMakeLists.txt | 2 +- app/node/generator/noise/noise.cpp | 2 +- app/node/generator/noise/noise.h | 2 +- app/node/generator/polygon/CMakeLists.txt | 2 +- app/node/generator/polygon/polygon.cpp | 2 +- app/node/generator/polygon/polygon.h | 2 +- app/node/generator/shape/CMakeLists.txt | 2 +- app/node/generator/shape/generatorwithmerge.cpp | 2 +- app/node/generator/shape/generatorwithmerge.h | 2 +- app/node/generator/shape/shapenode.cpp | 2 +- app/node/generator/shape/shapenode.h | 2 +- app/node/generator/shape/shapenodebase.cpp | 2 +- app/node/generator/shape/shapenodebase.h | 2 +- app/node/generator/solid/CMakeLists.txt | 2 +- app/node/generator/solid/solid.cpp | 2 +- app/node/generator/solid/solid.h | 2 +- app/node/generator/text/CMakeLists.txt | 2 +- app/node/generator/text/textv1.cpp | 2 +- app/node/generator/text/textv1.h | 2 +- app/node/generator/text/textv2.cpp | 2 +- app/node/generator/text/textv2.h | 2 +- app/node/generator/text/textv3.cpp | 2 +- app/node/generator/text/textv3.h | 2 +- app/node/gizmo/CMakeLists.txt | 2 +- app/node/gizmo/draggable.cpp | 2 +- app/node/gizmo/draggable.h | 2 +- app/node/gizmo/gizmo.cpp | 2 +- app/node/gizmo/gizmo.h | 2 +- app/node/gizmo/line.cpp | 2 +- app/node/gizmo/line.h | 2 +- app/node/gizmo/path.cpp | 2 +- app/node/gizmo/path.h | 2 +- app/node/gizmo/point.cpp | 2 +- app/node/gizmo/point.h | 2 +- app/node/gizmo/polygon.cpp | 2 +- app/node/gizmo/polygon.h | 2 +- app/node/gizmo/screen.cpp | 2 +- app/node/gizmo/screen.h | 2 +- app/node/gizmo/text.cpp | 2 +- app/node/gizmo/text.h | 2 +- app/node/globals.cpp | 2 +- app/node/globals.h | 2 +- app/node/graph.cpp | 2 +- app/node/graph.h | 2 +- app/node/group/CMakeLists.txt | 2 +- app/node/group/group.cpp | 2 +- app/node/group/group.h | 2 +- app/node/hashtraverser.cpp | 2 +- app/node/hashtraverser.h | 2 +- app/node/input/CMakeLists.txt | 2 +- app/node/input/time/CMakeLists.txt | 2 +- app/node/input/time/timeinput.cpp | 2 +- app/node/input/time/timeinput.h | 2 +- app/node/input/value/CMakeLists.txt | 2 +- app/node/input/value/valuenode.cpp | 2 +- app/node/input/value/valuenode.h | 2 +- app/node/inputdragger.cpp | 2 +- app/node/inputdragger.h | 2 +- app/node/inputimmediate.cpp | 2 +- app/node/inputimmediate.h | 2 +- app/node/keyframe.cpp | 2 +- app/node/keyframe.h | 2 +- app/node/keying/CMakeLists.txt | 2 +- app/node/keying/chromakey/CMakeLists.txt | 2 +- app/node/keying/colordifferencekey/CMakeLists.txt | 2 +- app/node/keying/despill/CMakeLists.txt | 2 +- app/node/math/CMakeLists.txt | 2 +- app/node/math/math/CMakeLists.txt | 2 +- app/node/math/math/math.cpp | 2 +- app/node/math/math/math.h | 2 +- app/node/math/math/mathbase.cpp | 2 +- app/node/math/math/mathbase.h | 2 +- app/node/math/merge/CMakeLists.txt | 2 +- app/node/math/merge/merge.cpp | 2 +- app/node/math/merge/merge.h | 2 +- app/node/math/trigonometry/CMakeLists.txt | 2 +- app/node/math/trigonometry/trigonometry.cpp | 2 +- app/node/math/trigonometry/trigonometry.h | 2 +- app/node/node.cpp | 2 +- app/node/node.h | 2 +- app/node/output/CMakeLists.txt | 2 +- app/node/output/track/CMakeLists.txt | 2 +- app/node/output/track/track.cpp | 2 +- app/node/output/track/track.h | 2 +- app/node/output/track/tracklist.cpp | 2 +- app/node/output/track/tracklist.h | 2 +- app/node/output/viewer/CMakeLists.txt | 2 +- app/node/output/viewer/viewer.cpp | 2 +- app/node/output/viewer/viewer.h | 2 +- app/node/param.cpp | 2 +- app/node/param.h | 2 +- app/node/project/CMakeLists.txt | 2 +- app/node/project/folder/CMakeLists.txt | 2 +- app/node/project/folder/folder.cpp | 2 +- app/node/project/folder/folder.h | 2 +- app/node/project/footage/CMakeLists.txt | 2 +- app/node/project/footage/footage.cpp | 2 +- app/node/project/footage/footage.h | 2 +- app/node/project/footage/footagedescription.cpp | 2 +- app/node/project/footage/footagedescription.h | 2 +- app/node/project/project.cpp | 2 +- app/node/project/project.h | 2 +- app/node/project/projectsettings/CMakeLists.txt | 2 +- app/node/project/projectsettings/projectsettings.cpp | 2 +- app/node/project/projectsettings/projectsettings.h | 2 +- app/node/project/projectviewmodel.cpp | 2 +- app/node/project/projectviewmodel.h | 2 +- app/node/project/sequence/CMakeLists.txt | 2 +- app/node/project/sequence/sequence.cpp | 2 +- app/node/project/sequence/sequence.h | 2 +- app/node/project/serializer/CMakeLists.txt | 2 +- app/node/project/serializer/serializer.cpp | 2 +- app/node/project/serializer/serializer.h | 2 +- app/node/project/serializer/serializer190219.cpp | 2 +- app/node/project/serializer/serializer190219.h | 2 +- app/node/project/serializer/serializer210528.cpp | 2 +- app/node/project/serializer/serializer210528.h | 2 +- app/node/project/serializer/serializer210907.cpp | 2 +- app/node/project/serializer/serializer210907.h | 2 +- app/node/project/serializer/serializer211228.cpp | 2 +- app/node/project/serializer/serializer211228.h | 2 +- app/node/project/serializer/serializer220403.cpp | 2 +- app/node/project/serializer/serializer220403.h | 2 +- app/node/splitvalue.h | 2 +- app/node/time/CMakeLists.txt | 2 +- app/node/time/timeoffset/CMakeLists.txt | 2 +- app/node/time/timeoffset/timeoffsetnode.cpp | 2 +- app/node/time/timeoffset/timeoffsetnode.h | 2 +- app/node/time/timeremap/CMakeLists.txt | 2 +- app/node/time/timeremap/timeremap.cpp | 2 +- app/node/time/timeremap/timeremap.h | 2 +- app/node/traverser.cpp | 2 +- app/node/traverser.h | 2 +- app/node/value.cpp | 2 +- app/node/value.h | 2 +- app/node/valuedatabase.cpp | 2 +- app/node/valuedatabase.h | 2 +- app/packaging/CMakeLists.txt | 2 +- app/packaging/linux/CMakeLists.txt | 2 +- app/panel/CMakeLists.txt | 2 +- app/panel/audiomonitor/CMakeLists.txt | 2 +- app/panel/audiomonitor/audiomonitor.cpp | 2 +- app/panel/audiomonitor/audiomonitor.h | 2 +- app/panel/curve/CMakeLists.txt | 2 +- app/panel/curve/curve.cpp | 2 +- app/panel/curve/curve.h | 2 +- app/panel/footageviewer/CMakeLists.txt | 2 +- app/panel/footageviewer/footageviewer.cpp | 2 +- app/panel/footageviewer/footageviewer.h | 2 +- app/panel/node/CMakeLists.txt | 2 +- app/panel/node/node.cpp | 2 +- app/panel/node/node.h | 2 +- app/panel/panelmanager.cpp | 2 +- app/panel/panelmanager.h | 2 +- app/panel/param/CMakeLists.txt | 2 +- app/panel/param/param.cpp | 2 +- app/panel/param/param.h | 2 +- app/panel/pixelsampler/CMakeLists.txt | 2 +- app/panel/pixelsampler/pixelsamplerpanel.cpp | 2 +- app/panel/pixelsampler/pixelsamplerpanel.h | 2 +- app/panel/project/CMakeLists.txt | 2 +- app/panel/project/footagemanagementpanel.h | 2 +- app/panel/project/project.cpp | 2 +- app/panel/project/project.h | 2 +- app/panel/scope/CMakeLists.txt | 2 +- app/panel/scope/scope.cpp | 2 +- app/panel/scope/scope.h | 2 +- app/panel/sequenceviewer/CMakeLists.txt | 2 +- app/panel/sequenceviewer/sequenceviewer.cpp | 2 +- app/panel/sequenceviewer/sequenceviewer.h | 2 +- app/panel/table/CMakeLists.txt | 2 +- app/panel/table/table.cpp | 2 +- app/panel/table/table.h | 2 +- app/panel/taskmanager/CMakeLists.txt | 2 +- app/panel/taskmanager/taskmanager.cpp | 2 +- app/panel/taskmanager/taskmanager.h | 2 +- app/panel/timebased/CMakeLists.txt | 2 +- app/panel/timebased/timebased.cpp | 2 +- app/panel/timebased/timebased.h | 2 +- app/panel/timeline/CMakeLists.txt | 2 +- app/panel/timeline/timeline.cpp | 2 +- app/panel/timeline/timeline.h | 2 +- app/panel/tool/CMakeLists.txt | 2 +- app/panel/tool/tool.cpp | 2 +- app/panel/tool/tool.h | 2 +- app/panel/viewer/CMakeLists.txt | 2 +- app/panel/viewer/viewer.cpp | 2 +- app/panel/viewer/viewer.h | 2 +- app/panel/viewer/viewerbase.cpp | 2 +- app/panel/viewer/viewerbase.h | 2 +- app/render/CMakeLists.txt | 2 +- app/render/alphaassoc.h | 2 +- app/render/audioparams.cpp | 2 +- app/render/audioparams.h | 2 +- app/render/audioplaybackcache.cpp | 2 +- app/render/audioplaybackcache.h | 2 +- app/render/color.cpp | 2 +- app/render/color.h | 2 +- app/render/colorprocessor.cpp | 2 +- app/render/colorprocessor.h | 2 +- app/render/colorprocessorcache.h | 2 +- app/render/colortransform.h | 2 +- app/render/diskmanager.cpp | 2 +- app/render/diskmanager.h | 2 +- app/render/framehashcache.cpp | 2 +- app/render/framehashcache.h | 2 +- app/render/framemanager.cpp | 2 +- app/render/framemanager.h | 2 +- app/render/job/CMakeLists.txt | 2 +- app/render/job/acceleratedjob.cpp | 2 +- app/render/job/acceleratedjob.h | 2 +- app/render/job/colortransformjob.h | 2 +- app/render/job/footagejob.h | 2 +- app/render/job/generatejob.h | 2 +- app/render/job/samplejob.h | 2 +- app/render/job/shaderjob.h | 2 +- app/render/managedcolor.cpp | 2 +- app/render/managedcolor.h | 2 +- app/render/ocioconf/CMakeLists.txt | 2 +- app/render/opengl/CMakeLists.txt | 2 +- app/render/opengl/openglrenderer.cpp | 2 +- app/render/opengl/openglrenderer.h | 2 +- app/render/playbackcache.cpp | 2 +- app/render/playbackcache.h | 2 +- app/render/previewaudiodevice.cpp | 2 +- app/render/previewaudiodevice.h | 2 +- app/render/previewautocacher.cpp | 2 +- app/render/previewautocacher.h | 2 +- app/render/rendercache.h | 2 +- app/render/renderer.cpp | 2 +- app/render/renderer.h | 2 +- app/render/rendererthreadwrapper.cpp | 2 +- app/render/rendererthreadwrapper.h | 2 +- app/render/renderjobtracker.cpp | 2 +- app/render/renderjobtracker.h | 2 +- app/render/rendermanager.cpp | 2 +- app/render/rendermanager.h | 2 +- app/render/rendermodes.h | 2 +- app/render/renderprocessor.cpp | 2 +- app/render/renderprocessor.h | 2 +- app/render/shadercode.h | 2 +- app/render/subtitleparams.cpp | 2 +- app/render/subtitleparams.h | 2 +- app/render/texture.cpp | 2 +- app/render/texture.h | 2 +- app/render/videoparams.cpp | 2 +- app/render/videoparams.h | 2 +- app/shaders/CMakeLists.txt | 2 +- app/task/CMakeLists.txt | 2 +- app/task/conform/CMakeLists.txt | 2 +- app/task/conform/conform.cpp | 2 +- app/task/conform/conform.h | 2 +- app/task/customcache/CMakeLists.txt | 2 +- app/task/customcache/customcachetask.cpp | 2 +- app/task/customcache/customcachetask.h | 2 +- app/task/export/CMakeLists.txt | 2 +- app/task/export/export.cpp | 2 +- app/task/export/export.h | 2 +- app/task/export/exportparams.cpp | 2 +- app/task/export/exportparams.h | 2 +- app/task/precache/CMakeLists.txt | 2 +- app/task/precache/precachetask.cpp | 2 +- app/task/precache/precachetask.h | 2 +- app/task/project/CMakeLists.txt | 2 +- app/task/project/import/CMakeLists.txt | 2 +- app/task/project/import/import.cpp | 2 +- app/task/project/import/import.h | 2 +- app/task/project/import/importerrordialog.cpp | 2 +- app/task/project/import/importerrordialog.h | 2 +- app/task/project/load/CMakeLists.txt | 2 +- app/task/project/load/load.cpp | 2 +- app/task/project/load/load.h | 2 +- app/task/project/load/loadbasetask.cpp | 2 +- app/task/project/load/loadbasetask.h | 2 +- app/task/project/loadotio/CMakeLists.txt | 2 +- app/task/project/loadotio/loadotio.cpp | 2 +- app/task/project/loadotio/loadotio.h | 2 +- app/task/project/save/CMakeLists.txt | 2 +- app/task/project/save/save.cpp | 2 +- app/task/project/save/save.h | 2 +- app/task/project/saveotio/CMakeLists.txt | 2 +- app/task/project/saveotio/saveotio.cpp | 2 +- app/task/project/saveotio/saveotio.h | 2 +- app/task/render/CMakeLists.txt | 2 +- app/task/render/render.cpp | 2 +- app/task/render/render.h | 2 +- app/task/task.h | 2 +- app/task/taskmanager.cpp | 2 +- app/task/taskmanager.h | 2 +- app/threading/CMakeLists.txt | 2 +- app/threading/threadpool.cpp | 2 +- app/threading/threadpool.h | 2 +- app/threading/threadticket.cpp | 2 +- app/threading/threadticket.h | 2 +- app/threading/threadticketwatcher.cpp | 2 +- app/threading/threadticketwatcher.h | 2 +- app/timeline/CMakeLists.txt | 2 +- app/timeline/timelinecommon.h | 2 +- app/timeline/timelinecoordinate.cpp | 2 +- app/timeline/timelinecoordinate.h | 2 +- app/timeline/timelinemarker.cpp | 2 +- app/timeline/timelinemarker.h | 2 +- app/timeline/timelinepoints.cpp | 2 +- app/timeline/timelinepoints.h | 2 +- app/timeline/timelineworkarea.cpp | 2 +- app/timeline/timelineworkarea.h | 2 +- app/tool/CMakeLists.txt | 2 +- app/tool/tool.h | 2 +- app/ts/CMakeLists.txt | 2 +- app/ui/CMakeLists.txt | 2 +- app/ui/colorcoding.cpp | 2 +- app/ui/colorcoding.h | 2 +- app/ui/cursors/CMakeLists.txt | 2 +- app/ui/graphics/CMakeLists.txt | 2 +- app/ui/icons/CMakeLists.txt | 2 +- app/ui/icons/icons.cpp | 2 +- app/ui/icons/icons.h | 2 +- app/ui/style/CMakeLists.txt | 2 +- app/ui/style/generate-style.sh | 2 +- app/ui/style/olive-dark/CMakeLists.txt | 2 +- app/ui/style/olive-dark/style.css | 2 +- app/ui/style/olive-light/CMakeLists.txt | 2 +- app/ui/style/olive-light/style.css | 2 +- app/ui/style/olive-light/svg/convert-to-dark.sh | 2 +- app/ui/style/style.cpp | 2 +- app/ui/style/style.h | 2 +- app/undo/CMakeLists.txt | 2 +- app/undo/undocommand.cpp | 2 +- app/undo/undocommand.h | 2 +- app/undo/undostack.cpp | 2 +- app/undo/undostack.h | 2 +- app/version.cpp | 2 +- app/version.h | 2 +- app/widget/CMakeLists.txt | 2 +- app/widget/audiomonitor/CMakeLists.txt | 2 +- app/widget/audiomonitor/audiomonitor.cpp | 2 +- app/widget/audiomonitor/audiomonitor.h | 2 +- app/widget/bezier/CMakeLists.txt | 2 +- app/widget/bezier/bezierwidget.cpp | 2 +- app/widget/bezier/bezierwidget.h | 2 +- app/widget/clickablelabel/CMakeLists.txt | 2 +- app/widget/clickablelabel/clickablelabel.cpp | 2 +- app/widget/clickablelabel/clickablelabel.h | 2 +- app/widget/collapsebutton/CMakeLists.txt | 2 +- app/widget/collapsebutton/collapsebutton.cpp | 2 +- app/widget/collapsebutton/collapsebutton.h | 2 +- app/widget/colorbutton/CMakeLists.txt | 2 +- app/widget/colorbutton/colorbutton.cpp | 2 +- app/widget/colorbutton/colorbutton.h | 2 +- app/widget/colorlabelmenu/CMakeLists.txt | 2 +- app/widget/colorlabelmenu/colorcodingcombobox.cpp | 2 +- app/widget/colorlabelmenu/colorcodingcombobox.h | 2 +- app/widget/colorlabelmenu/colorlabelmenu.cpp | 2 +- app/widget/colorlabelmenu/colorlabelmenu.h | 2 +- app/widget/colorlabelmenu/colorlabelmenuitem.cpp | 2 +- app/widget/colorlabelmenu/colorlabelmenuitem.h | 2 +- app/widget/colorwheel/CMakeLists.txt | 2 +- app/widget/colorwheel/colorgradientwidget.cpp | 2 +- app/widget/colorwheel/colorgradientwidget.h | 2 +- app/widget/colorwheel/colorpreviewbox.cpp | 2 +- app/widget/colorwheel/colorpreviewbox.h | 2 +- app/widget/colorwheel/colorspacechooser.cpp | 2 +- app/widget/colorwheel/colorspacechooser.h | 2 +- app/widget/colorwheel/colorswatchwidget.cpp | 2 +- app/widget/colorwheel/colorswatchwidget.h | 2 +- app/widget/colorwheel/colorvalueswidget.cpp | 2 +- app/widget/colorwheel/colorvalueswidget.h | 2 +- app/widget/colorwheel/colorwheelwidget.cpp | 2 +- app/widget/colorwheel/colorwheelwidget.h | 2 +- app/widget/columnedgridlayout/CMakeLists.txt | 2 +- app/widget/columnedgridlayout/columnedgridlayout.cpp | 2 +- app/widget/columnedgridlayout/columnedgridlayout.h | 2 +- app/widget/curvewidget/CMakeLists.txt | 2 +- app/widget/curvewidget/curveview.cpp | 2 +- app/widget/curvewidget/curveview.h | 2 +- app/widget/curvewidget/curvewidget.cpp | 2 +- app/widget/curvewidget/curvewidget.h | 2 +- app/widget/filefield/CMakeLists.txt | 2 +- app/widget/filefield/filefield.cpp | 2 +- app/widget/filefield/filefield.h | 2 +- app/widget/flowlayout/CMakeLists.txt | 2 +- app/widget/focusablelineedit/CMakeLists.txt | 2 +- app/widget/focusablelineedit/focusablelineedit.cpp | 2 +- app/widget/focusablelineedit/focusablelineedit.h | 2 +- app/widget/handmovableview/CMakeLists.txt | 2 +- app/widget/handmovableview/handmovableview.cpp | 2 +- app/widget/handmovableview/handmovableview.h | 2 +- app/widget/keyframeview/CMakeLists.txt | 2 +- app/widget/keyframeview/keyframeview.cpp | 2 +- app/widget/keyframeview/keyframeview.h | 2 +- app/widget/keyframeview/keyframeviewinputconnection.cpp | 2 +- app/widget/keyframeview/keyframeviewinputconnection.h | 2 +- app/widget/keyframeview/keyframeviewundo.cpp | 2 +- app/widget/keyframeview/keyframeviewundo.h | 2 +- app/widget/manageddisplay/CMakeLists.txt | 2 +- app/widget/manageddisplay/manageddisplay.cpp | 2 +- app/widget/manageddisplay/manageddisplay.h | 2 +- app/widget/menu/CMakeLists.txt | 2 +- app/widget/menu/menu.cpp | 2 +- app/widget/menu/menu.h | 2 +- app/widget/menu/menushared.cpp | 2 +- app/widget/menu/menushared.h | 2 +- app/widget/nodecombobox/CMakeLists.txt | 2 +- app/widget/nodecombobox/nodecombobox.cpp | 2 +- app/widget/nodecombobox/nodecombobox.h | 2 +- app/widget/nodeparamview/CMakeLists.txt | 2 +- app/widget/nodeparamview/nodeparamview.cpp | 2 +- app/widget/nodeparamview/nodeparamview.h | 2 +- app/widget/nodeparamview/nodeparamviewarraywidget.cpp | 2 +- app/widget/nodeparamview/nodeparamviewarraywidget.h | 2 +- app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp | 2 +- app/widget/nodeparamview/nodeparamviewconnectedlabel.h | 2 +- app/widget/nodeparamview/nodeparamviewcontext.cpp | 2 +- app/widget/nodeparamview/nodeparamviewcontext.h | 2 +- app/widget/nodeparamview/nodeparamviewdockarea.cpp | 2 +- app/widget/nodeparamview/nodeparamviewdockarea.h | 2 +- app/widget/nodeparamview/nodeparamviewitem.cpp | 2 +- app/widget/nodeparamview/nodeparamviewitem.h | 2 +- app/widget/nodeparamview/nodeparamviewitembase.cpp | 2 +- app/widget/nodeparamview/nodeparamviewitembase.h | 2 +- app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp | 2 +- app/widget/nodeparamview/nodeparamviewitemtitlebar.h | 2 +- .../nodeparamview/nodeparamviewkeyframecontrol.cpp | 2 +- app/widget/nodeparamview/nodeparamviewkeyframecontrol.h | 2 +- app/widget/nodeparamview/nodeparamviewtextedit.cpp | 2 +- app/widget/nodeparamview/nodeparamviewtextedit.h | 2 +- app/widget/nodeparamview/nodeparamviewundo.cpp | 2 +- app/widget/nodeparamview/nodeparamviewundo.h | 2 +- app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp | 2 +- app/widget/nodeparamview/nodeparamviewwidgetbridge.h | 2 +- app/widget/nodetableview/CMakeLists.txt | 2 +- app/widget/nodetableview/nodetableview.cpp | 2 +- app/widget/nodetableview/nodetableview.h | 2 +- app/widget/nodetableview/nodetablewidget.cpp | 2 +- app/widget/nodetableview/nodetablewidget.h | 2 +- app/widget/nodetreeview/CMakeLists.txt | 2 +- app/widget/nodetreeview/nodetreeview.cpp | 2 +- app/widget/nodetreeview/nodetreeview.h | 2 +- app/widget/nodevaluetree/CMakeLists.txt | 2 +- app/widget/nodeview/CMakeLists.txt | 2 +- app/widget/nodeview/nodeview.cpp | 2 +- app/widget/nodeview/nodeview.h | 2 +- app/widget/nodeview/nodeviewcommon.h | 2 +- app/widget/nodeview/nodeviewedge.cpp | 2 +- app/widget/nodeview/nodeviewedge.h | 2 +- app/widget/nodeview/nodeviewitem.cpp | 2 +- app/widget/nodeview/nodeviewitem.h | 2 +- app/widget/nodeview/nodeviewitemconnector.cpp | 2 +- app/widget/nodeview/nodeviewitemconnector.h | 2 +- app/widget/nodeview/nodeviewminimap.cpp | 2 +- app/widget/nodeview/nodeviewminimap.h | 2 +- app/widget/nodeview/nodeviewscene.cpp | 2 +- app/widget/nodeview/nodeviewscene.h | 2 +- app/widget/nodeview/nodeviewundo.cpp | 2 +- app/widget/nodeview/nodeviewundo.h | 2 +- app/widget/nodeview/nodewidget.cpp | 2 +- app/widget/nodeview/nodewidget.h | 2 +- app/widget/panel/CMakeLists.txt | 2 +- app/widget/panel/panel.cpp | 2 +- app/widget/panel/panel.h | 2 +- app/widget/path/CMakeLists.txt | 2 +- app/widget/path/pathwidget.cpp | 2 +- app/widget/path/pathwidget.h | 2 +- app/widget/pixelsampler/CMakeLists.txt | 2 +- app/widget/pixelsampler/pixelsampler.cpp | 2 +- app/widget/pixelsampler/pixelsampler.h | 2 +- app/widget/playbackcontrols/CMakeLists.txt | 2 +- app/widget/playbackcontrols/dragbutton.cpp | 2 +- app/widget/playbackcontrols/dragbutton.h | 2 +- app/widget/playbackcontrols/playbackcontrols.cpp | 2 +- app/widget/playbackcontrols/playbackcontrols.h | 2 +- app/widget/projectexplorer/CMakeLists.txt | 2 +- app/widget/projectexplorer/projectexplorer.cpp | 2 +- app/widget/projectexplorer/projectexplorer.h | 2 +- app/widget/projectexplorer/projectexplorericonview.cpp | 2 +- app/widget/projectexplorer/projectexplorericonview.h | 2 +- .../projectexplorericonviewitemdelegate.cpp | 2 +- .../projectexplorericonviewitemdelegate.h | 2 +- app/widget/projectexplorer/projectexplorerlistview.cpp | 2 +- app/widget/projectexplorer/projectexplorerlistview.h | 2 +- .../projectexplorer/projectexplorerlistviewbase.cpp | 2 +- app/widget/projectexplorer/projectexplorerlistviewbase.h | 2 +- .../projectexplorerlistviewitemdelegate.cpp | 2 +- .../projectexplorerlistviewitemdelegate.h | 2 +- app/widget/projectexplorer/projectexplorernavigation.cpp | 2 +- app/widget/projectexplorer/projectexplorernavigation.h | 2 +- app/widget/projectexplorer/projectexplorertreeview.cpp | 2 +- app/widget/projectexplorer/projectexplorertreeview.h | 2 +- app/widget/projectexplorer/projectexplorerundo.h | 2 +- app/widget/projecttoolbar/CMakeLists.txt | 2 +- app/widget/projecttoolbar/projecttoolbar.cpp | 2 +- app/widget/projecttoolbar/projecttoolbar.h | 2 +- app/widget/resizablescrollbar/CMakeLists.txt | 2 +- app/widget/resizablescrollbar/resizablescrollbar.cpp | 2 +- app/widget/resizablescrollbar/resizablescrollbar.h | 2 +- .../resizablescrollbar/resizabletimelinescrollbar.cpp | 2 +- .../resizablescrollbar/resizabletimelinescrollbar.h | 2 +- app/widget/scope/CMakeLists.txt | 2 +- app/widget/scope/histogram/CMakeLists.txt | 2 +- app/widget/scope/histogram/histogram.cpp | 2 +- app/widget/scope/histogram/histogram.h | 2 +- app/widget/scope/scopebase/CMakeLists.txt | 2 +- app/widget/scope/scopebase/scopebase.cpp | 2 +- app/widget/scope/scopebase/scopebase.h | 2 +- app/widget/scope/waveform/CMakeLists.txt | 2 +- app/widget/scope/waveform/waveform.cpp | 2 +- app/widget/scope/waveform/waveform.h | 2 +- app/widget/slider/CMakeLists.txt | 2 +- app/widget/slider/base/CMakeLists.txt | 2 +- app/widget/slider/base/decimalsliderbase.cpp | 2 +- app/widget/slider/base/decimalsliderbase.h | 2 +- app/widget/slider/base/numericsliderbase.cpp | 2 +- app/widget/slider/base/numericsliderbase.h | 2 +- app/widget/slider/base/sliderbase.cpp | 2 +- app/widget/slider/base/sliderbase.h | 2 +- app/widget/slider/base/sliderlabel.cpp | 2 +- app/widget/slider/base/sliderlabel.h | 2 +- app/widget/slider/base/sliderladder.cpp | 2 +- app/widget/slider/base/sliderladder.h | 2 +- app/widget/slider/floatslider.cpp | 2 +- app/widget/slider/floatslider.h | 2 +- app/widget/slider/integerslider.cpp | 2 +- app/widget/slider/integerslider.h | 2 +- app/widget/slider/rationalslider.cpp | 2 +- app/widget/slider/rationalslider.h | 2 +- app/widget/slider/stringslider.cpp | 2 +- app/widget/slider/stringslider.h | 2 +- app/widget/slider/timeslider.cpp | 2 +- app/widget/slider/timeslider.h | 2 +- app/widget/standardcombos/CMakeLists.txt | 2 +- app/widget/standardcombos/channellayoutcombobox.h | 2 +- app/widget/standardcombos/frameratecombobox.h | 2 +- app/widget/standardcombos/interlacedcombobox.h | 2 +- app/widget/standardcombos/pixelaspectratiocombobox.h | 2 +- app/widget/standardcombos/pixelformatcombobox.h | 2 +- app/widget/standardcombos/sampleformatcombobox.h | 2 +- app/widget/standardcombos/sampleratecombobox.h | 2 +- app/widget/standardcombos/standardcombos.h | 2 +- app/widget/standardcombos/videodividercombobox.h | 2 +- app/widget/taskview/CMakeLists.txt | 2 +- app/widget/taskview/elapsedcounterwidget.cpp | 2 +- app/widget/taskview/elapsedcounterwidget.h | 2 +- app/widget/taskview/taskview.cpp | 2 +- app/widget/taskview/taskview.h | 2 +- app/widget/taskview/taskviewitem.cpp | 2 +- app/widget/taskview/taskviewitem.h | 2 +- app/widget/timebased/CMakeLists.txt | 2 +- app/widget/timebased/timebasedview.cpp | 2 +- app/widget/timebased/timebasedview.h | 2 +- app/widget/timebased/timebasedviewselectionmanager.cpp | 2 +- app/widget/timebased/timebasedviewselectionmanager.h | 2 +- app/widget/timebased/timebasedwidget.cpp | 2 +- app/widget/timebased/timebasedwidget.h | 2 +- app/widget/timebased/timescaledobject.cpp | 2 +- app/widget/timebased/timescaledobject.h | 2 +- app/widget/timelinewidget/CMakeLists.txt | 2 +- app/widget/timelinewidget/timelineandtrackview.cpp | 2 +- app/widget/timelinewidget/timelineandtrackview.h | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 2 +- app/widget/timelinewidget/timelinewidget.h | 2 +- app/widget/timelinewidget/timelinewidgetselections.cpp | 2 +- app/widget/timelinewidget/timelinewidgetselections.h | 2 +- app/widget/timelinewidget/tool/CMakeLists.txt | 2 +- app/widget/timelinewidget/tool/add.cpp | 2 +- app/widget/timelinewidget/tool/add.h | 2 +- app/widget/timelinewidget/tool/beam.cpp | 2 +- app/widget/timelinewidget/tool/beam.h | 2 +- app/widget/timelinewidget/tool/edit.cpp | 2 +- app/widget/timelinewidget/tool/edit.h | 2 +- app/widget/timelinewidget/tool/import.cpp | 2 +- app/widget/timelinewidget/tool/import.h | 2 +- app/widget/timelinewidget/tool/pointer.cpp | 2 +- app/widget/timelinewidget/tool/pointer.h | 2 +- app/widget/timelinewidget/tool/razor.cpp | 2 +- app/widget/timelinewidget/tool/razor.h | 2 +- app/widget/timelinewidget/tool/record.h | 2 +- app/widget/timelinewidget/tool/ripple.cpp | 2 +- app/widget/timelinewidget/tool/ripple.h | 2 +- app/widget/timelinewidget/tool/rolling.cpp | 2 +- app/widget/timelinewidget/tool/rolling.h | 2 +- app/widget/timelinewidget/tool/slide.cpp | 2 +- app/widget/timelinewidget/tool/slide.h | 2 +- app/widget/timelinewidget/tool/slip.cpp | 2 +- app/widget/timelinewidget/tool/slip.h | 2 +- app/widget/timelinewidget/tool/tool.cpp | 2 +- app/widget/timelinewidget/tool/tool.h | 2 +- app/widget/timelinewidget/tool/trackselect.cpp | 2 +- app/widget/timelinewidget/tool/trackselect.h | 2 +- app/widget/timelinewidget/tool/transition.cpp | 2 +- app/widget/timelinewidget/tool/transition.h | 2 +- app/widget/timelinewidget/tool/zoom.cpp | 2 +- app/widget/timelinewidget/tool/zoom.h | 2 +- app/widget/timelinewidget/trackview/CMakeLists.txt | 2 +- app/widget/timelinewidget/trackview/trackview.cpp | 2 +- app/widget/timelinewidget/trackview/trackview.h | 2 +- app/widget/timelinewidget/trackview/trackviewitem.cpp | 2 +- app/widget/timelinewidget/trackview/trackviewitem.h | 2 +- .../timelinewidget/trackview/trackviewsplitter.cpp | 2 +- app/widget/timelinewidget/trackview/trackviewsplitter.h | 2 +- app/widget/timelinewidget/undo/CMakeLists.txt | 2 +- app/widget/timelinewidget/undo/timelineundocommon.h | 2 +- app/widget/timelinewidget/undo/timelineundogeneral.cpp | 2 +- app/widget/timelinewidget/undo/timelineundogeneral.h | 2 +- app/widget/timelinewidget/undo/timelineundopointer.cpp | 2 +- app/widget/timelinewidget/undo/timelineundopointer.h | 2 +- app/widget/timelinewidget/undo/timelineundoripple.cpp | 2 +- app/widget/timelinewidget/undo/timelineundoripple.h | 2 +- app/widget/timelinewidget/undo/timelineundosplit.cpp | 2 +- app/widget/timelinewidget/undo/timelineundosplit.h | 2 +- app/widget/timelinewidget/undo/timelineundotrack.cpp | 2 +- app/widget/timelinewidget/undo/timelineundotrack.h | 2 +- app/widget/timelinewidget/undo/timelineundoworkarea.cpp | 2 +- app/widget/timelinewidget/undo/timelineundoworkarea.h | 2 +- app/widget/timelinewidget/view/CMakeLists.txt | 2 +- app/widget/timelinewidget/view/timelineview.cpp | 2 +- app/widget/timelinewidget/view/timelineview.h | 2 +- app/widget/timelinewidget/view/timelineviewghostitem.h | 2 +- app/widget/timelinewidget/view/timelineviewmouseevent.h | 2 +- app/widget/timeruler/CMakeLists.txt | 2 +- app/widget/timeruler/seekablewidget.cpp | 2 +- app/widget/timeruler/seekablewidget.h | 2 +- app/widget/timeruler/timeruler.cpp | 2 +- app/widget/timeruler/timeruler.h | 2 +- app/widget/timetarget/CMakeLists.txt | 2 +- app/widget/timetarget/timetarget.cpp | 2 +- app/widget/timetarget/timetarget.h | 2 +- app/widget/toolbar/CMakeLists.txt | 2 +- app/widget/toolbar/toolbar.cpp | 2 +- app/widget/toolbar/toolbar.h | 2 +- app/widget/toolbar/toolbarbutton.cpp | 2 +- app/widget/toolbar/toolbarbutton.h | 2 +- app/widget/viewer/CMakeLists.txt | 2 +- app/widget/viewer/audiowaveformview.cpp | 2 +- app/widget/viewer/audiowaveformview.h | 2 +- app/widget/viewer/footageviewer.cpp | 2 +- app/widget/viewer/footageviewer.h | 2 +- app/widget/viewer/viewer.cpp | 2 +- app/widget/viewer/viewer.h | 2 +- app/widget/viewer/viewerdisplay.cpp | 2 +- app/widget/viewer/viewerdisplay.h | 2 +- app/widget/viewer/viewerplaybacktimer.cpp | 2 +- app/widget/viewer/viewerplaybacktimer.h | 2 +- app/widget/viewer/viewerqueue.h | 2 +- app/widget/viewer/viewersafemargininfo.h | 2 +- app/widget/viewer/viewersizer.cpp | 2 +- app/widget/viewer/viewersizer.h | 2 +- app/widget/viewer/viewertexteditor.cpp | 2 +- app/widget/viewer/viewertexteditor.h | 2 +- app/widget/viewer/viewerwindow.cpp | 2 +- app/widget/viewer/viewerwindow.h | 2 +- app/window/CMakeLists.txt | 2 +- app/window/mainwindow/CMakeLists.txt | 2 +- app/window/mainwindow/mainmenu.cpp | 2 +- app/window/mainwindow/mainmenu.h | 2 +- app/window/mainwindow/mainstatusbar.cpp | 2 +- app/window/mainwindow/mainstatusbar.h | 2 +- app/window/mainwindow/mainwindow.cpp | 2 +- app/window/mainwindow/mainwindow.h | 2 +- app/window/mainwindow/mainwindowundo.cpp | 2 +- app/window/mainwindow/mainwindowundo.h | 2 +- cmake/FindGoogleCrashpad.cmake | 2 +- cmake/FindOpenColorIO.cmake | 2 +- cmake/FindOpenTimelineIO.cmake | 2 +- tests/CMakeLists.txt | 2 +- tests/compositing/CMakeLists.txt | 2 +- tests/compositing/compositing-tests.cpp | 2 +- tests/general/CMakeLists.txt | 2 +- tests/general/common-tests.cpp | 2 +- tests/general/rational-tests.cpp | 2 +- tests/general/timerange-tests.cpp | 2 +- tests/testutil.h | 2 +- tests/timeline/CMakeLists.txt | 2 +- tests/timeline/timeline-tests.cpp | 2 +- update-copyright.sh | 9 +++++++++ 941 files changed, 950 insertions(+), 941 deletions(-) create mode 100644 update-copyright.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index acc43c97a..fbd783893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 64d77da16..d438dc135 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt index 08273c9af..6b161d81b 100644 --- a/app/audio/CMakeLists.txt +++ b/app/audio/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 3817f285a..fc0dc886d 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 2916d29d2..3c35e0593 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/audio/audioprocessor.cpp b/app/audio/audioprocessor.cpp index b9c95bb6d..d73ca0931 100644 --- a/app/audio/audioprocessor.cpp +++ b/app/audio/audioprocessor.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/audio/audioprocessor.h b/app/audio/audioprocessor.h index 38826c707..d2861a3bc 100644 --- a/app/audio/audioprocessor.h +++ b/app/audio/audioprocessor.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 7719d7090..378a5da69 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/audio/audiovisualwaveform.h b/app/audio/audiovisualwaveform.h index 446d67e75..389f72980 100644 --- a/app/audio/audiovisualwaveform.h +++ b/app/audio/audiovisualwaveform.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/CMakeLists.txt b/app/cli/CMakeLists.txt index b96b46395..c261a3e00 100644 --- a/app/cli/CMakeLists.txt +++ b/app/cli/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/cli/cliexport/cliexportmanager.cpp b/app/cli/cliexport/cliexportmanager.cpp index 7e84ed84c..9f9670996 100644 --- a/app/cli/cliexport/cliexportmanager.cpp +++ b/app/cli/cliexport/cliexportmanager.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/cliexport/cliexportmanager.h b/app/cli/cliexport/cliexportmanager.h index dee831bb7..867bc5493 100644 --- a/app/cli/cliexport/cliexportmanager.h +++ b/app/cli/cliexport/cliexportmanager.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/cliprogress/CMakeLists.txt b/app/cli/cliprogress/CMakeLists.txt index 0dcf47f90..621c498da 100644 --- a/app/cli/cliprogress/CMakeLists.txt +++ b/app/cli/cliprogress/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/cli/cliprogress/cliprogressdialog.cpp b/app/cli/cliprogress/cliprogressdialog.cpp index d4ffa0095..17203e386 100644 --- a/app/cli/cliprogress/cliprogressdialog.cpp +++ b/app/cli/cliprogress/cliprogressdialog.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/cliprogress/cliprogressdialog.h b/app/cli/cliprogress/cliprogressdialog.h index 1ed3c61f6..444c66f33 100644 --- a/app/cli/cliprogress/cliprogressdialog.h +++ b/app/cli/cliprogress/cliprogressdialog.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/clitask/CMakeLists.txt b/app/cli/clitask/CMakeLists.txt index 0c904acb7..8e5f4782b 100644 --- a/app/cli/clitask/CMakeLists.txt +++ b/app/cli/clitask/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/cli/clitask/clitaskdialog.cpp b/app/cli/clitask/clitaskdialog.cpp index 45288f283..d36ef367f 100644 --- a/app/cli/clitask/clitaskdialog.cpp +++ b/app/cli/clitask/clitaskdialog.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/cli/clitask/clitaskdialog.h b/app/cli/clitask/clitaskdialog.h index 5bdac7ad5..e07ffa6cc 100644 --- a/app/cli/clitask/clitaskdialog.h +++ b/app/cli/clitask/clitaskdialog.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/CMakeLists.txt b/app/codec/CMakeLists.txt index fa6e81c25..21733e110 100644 --- a/app/codec/CMakeLists.txt +++ b/app/codec/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index c487e2425..c793f55d0 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 3191ade23..35410c0d1 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 06b36c3bb..458c71d8e 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/encoder.h b/app/codec/encoder.h index f94473dbb..e1535290b 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/exportcodec.cpp b/app/codec/exportcodec.cpp index e1c76ad4c..2a908f8b5 100644 --- a/app/codec/exportcodec.cpp +++ b/app/codec/exportcodec.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/exportcodec.h b/app/codec/exportcodec.h index a57abc294..7fff8f311 100644 --- a/app/codec/exportcodec.h +++ b/app/codec/exportcodec.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/exportformat.cpp b/app/codec/exportformat.cpp index 14f3e8446..9b6997d78 100644 --- a/app/codec/exportformat.cpp +++ b/app/codec/exportformat.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/exportformat.h b/app/codec/exportformat.h index fcba9666e..0fbf1d203 100644 --- a/app/codec/exportformat.h +++ b/app/codec/exportformat.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index 0f9aa9838..70d978888 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index df6c874da..165807418 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index f3590b212..612aa8533 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 6c42df4da..40d2b13ef 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 465f7c40a..28786b9ce 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp index e8df84d67..1337b14e7 100644 --- a/app/codec/ffmpeg/ffmpegframepool.cpp +++ b/app/codec/ffmpeg/ffmpegframepool.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h index c3f9d050a..8107ec324 100644 --- a/app/codec/ffmpeg/ffmpegframepool.h +++ b/app/codec/ffmpeg/ffmpegframepool.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index 8701ce2ba..d6899d9ff 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/frame.h b/app/codec/frame.h index 5c1c4343a..995845782 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/oiio/CMakeLists.txt b/app/codec/oiio/CMakeLists.txt index c4d407fe6..33cc8e2bb 100644 --- a/app/codec/oiio/CMakeLists.txt +++ b/app/codec/oiio/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e93e434e5..08264ac56 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index a6039871d..fd9270e8f 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/oiio/oiioencoder.cpp b/app/codec/oiio/oiioencoder.cpp index 679dbd550..0b676615c 100644 --- a/app/codec/oiio/oiioencoder.cpp +++ b/app/codec/oiio/oiioencoder.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/oiio/oiioencoder.h b/app/codec/oiio/oiioencoder.h index 1099e68ca..27f6ee44a 100644 --- a/app/codec/oiio/oiioencoder.h +++ b/app/codec/oiio/oiioencoder.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/planarfiledevice.cpp b/app/codec/planarfiledevice.cpp index c68d3ba8c..f8c961998 100644 --- a/app/codec/planarfiledevice.cpp +++ b/app/codec/planarfiledevice.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/planarfiledevice.h b/app/codec/planarfiledevice.h index d02dafa19..00da3a454 100644 --- a/app/codec/planarfiledevice.h +++ b/app/codec/planarfiledevice.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index b7d93ec83..3c0096867 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h index fb785103a..b8976af07 100644 --- a/app/codec/samplebuffer.h +++ b/app/codec/samplebuffer.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index cc881d473..b4bdea348 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2022 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 diff --git a/app/common/autoscroll.h b/app/common/autoscroll.h index 4929348c7..8f35d3b55 100644 --- a/app/common/autoscroll.h +++ b/app/common/autoscroll.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/bezier.cpp b/app/common/bezier.cpp index b1a5f5fba..0636df355 100644 --- a/app/common/bezier.cpp +++ b/app/common/bezier.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/bezier.h b/app/common/bezier.h index 8d20a75ec..52b984a04 100644 --- a/app/common/bezier.h +++ b/app/common/bezier.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/cancelableobject.h b/app/common/cancelableobject.h index 4eafbb547..d7a1fe5d8 100644 --- a/app/common/cancelableobject.h +++ b/app/common/cancelableobject.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/channellayout.h b/app/common/channellayout.h index 07e5994e2..830726cae 100644 --- a/app/common/channellayout.h +++ b/app/common/channellayout.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/clamp.h b/app/common/clamp.h index 0ab1ffd4a..9f8cf78d2 100644 --- a/app/common/clamp.h +++ b/app/common/clamp.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/common/commandlineparser.cpp b/app/common/commandlineparser.cpp index 150bdc244..4d1187c04 100644 --- a/app/common/commandlineparser.cpp +++ b/app/common/commandlineparser.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 @@ -107,7 +107,7 @@ void CommandLineParser::PrintHelp(const char* filename) QCoreApplication::applicationName().toUtf8().constData(), QCoreApplication::applicationVersion().toUtf8().constData()); - printf("Copyright (C) 2018-2021 Olive Team\n"); + printf("Copyright (C) 2018-2022 Olive Team\n"); QString positional_args; for (int i=0; i Date: Tue, 10 May 2022 10:59:53 -0700 Subject: [PATCH 11/62] very minor formatting update --- app/threading/threadpool.cpp | 4 ++-- app/threading/threadpool.h | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index 24a90bf1d..b048f3519 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -22,8 +22,8 @@ namespace olive { -ThreadPool::ThreadPool(unsigned threads, QObject *parent) - : QObject(parent) +ThreadPool::ThreadPool(unsigned threads, QObject *parent) : + QObject(parent) { if (threads == 0) { threads = std::thread::hardware_concurrency(); diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index 7a7ea12df..3c3204bb1 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -40,10 +40,7 @@ public: using TaskType = RenderTicketPtr; ThreadPool(unsigned threads, QObject *parent); - ThreadPool(const ThreadPool &) = delete; - ThreadPool(ThreadPool &&) = delete; - ThreadPool & operator=(const ThreadPool&) = delete; - ThreadPool & operator=(ThreadPool &&) = delete; + DISABLE_COPY_MOVE(ThreadPool) virtual void RunTicket(RenderTicketPtr ticket) const = 0; void AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority = RenderTicketPriority::kNormal); From af9e54504c8233178d69cbae5a16680f85af2642 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 12:36:15 -0700 Subject: [PATCH 12/62] transformdistortnode: restore shortname override --- app/node/distort/transform/transformdistortnode.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index c8bf45105..7d1aa1b3b 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -41,6 +41,12 @@ public: return tr("Transform"); } + virtual QString ShortName() const override + { + // Override MatrixGenerator's short name "Ortho" + return Name(); + } + virtual QString id() const override { return QStringLiteral("org.olivevideoeditor.Olive.transform"); From 182e1599af0a074e6ed7cc07d456a05841e90734 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 15:42:09 -0700 Subject: [PATCH 13/62] keyframes: implemented copying/pasting --- app/node/keyframe.cpp | 5 + app/node/keyframe.h | 20 +- app/node/project/serializer/serializer.h | 10 +- .../project/serializer/serializer220403.cpp | 256 ++++++++++++++---- .../project/serializer/serializer220403.h | 4 + app/panel/timebased/timebased.cpp | 15 + app/panel/timebased/timebased.h | 6 + app/panel/timeline/timeline.cpp | 17 +- app/panel/timeline/timeline.h | 6 - app/widget/curvewidget/curveview.h | 5 + app/widget/curvewidget/curvewidget.cpp | 30 ++ app/widget/curvewidget/curvewidget.h | 6 + app/widget/keyframeview/keyframeview.cpp | 63 +++++ app/widget/keyframeview/keyframeview.h | 6 + .../keyframeviewinputconnection.h | 5 + app/widget/nodeparamview/nodeparamview.cpp | 86 +++++- app/widget/nodeparamview/nodeparamview.h | 6 + .../nodeparamview/nodeparamviewitembase.h | 2 + app/widget/timebased/timebasedwidget.cpp | 18 ++ app/widget/timebased/timebasedwidget.h | 4 + app/widget/timelinewidget/timelinewidget.cpp | 122 +++++---- app/widget/timelinewidget/timelinewidget.h | 8 +- app/widget/timeruler/seekablewidget.cpp | 25 +- app/widget/timeruler/seekablewidget.h | 2 +- 24 files changed, 567 insertions(+), 160 deletions(-) diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 968876021..eb6d5dea1 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -41,6 +41,11 @@ NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, Type typ setParent(parent); } +NodeKeyframe::NodeKeyframe() +{ + type_ = NodeKeyframe::kLinear; +} + NodeKeyframe::~NodeKeyframe() { setParent(nullptr); diff --git a/app/node/keyframe.h b/app/node/keyframe.h index 053b07298..a002aa8fc 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -64,6 +64,7 @@ public: * @brief NodeKeyframe Constructor */ NodeKeyframe(const rational& time, const QVariant& value, Type type, int track, int element, const QString& input, QObject* parent = nullptr); + NodeKeyframe(); virtual ~NodeKeyframe() override; @@ -71,10 +72,9 @@ public: NodeKeyframe* copy(QObject* parent = nullptr) const; Node* parent() const; - const QString& input() const - { - return input_; - } + + const QString& input() const { return input_; } + void set_input(const QString& input) { input_ = input; } NodeKeyframeTrackReference key_track_ref() const { @@ -141,15 +141,11 @@ public: * For the majority of keyfreames, this will be 0, but for some types, such as kVec2, this will be 0 for X keyframes * and 1 for Y keyframes, etc. */ - int track() const - { - return track_; - } + int track() const { return track_; } + void set_track(int t) { track_ = t; } - int element() const - { - return element_; - } + int element() const { return element_; } + void set_element(int e) { element_ = e; } /** * @brief Convenience function for getting the opposite handle type (e.g. kInHandle <-> kOutHandle) diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 1586891ef..831c98d44 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -55,6 +55,7 @@ public: }; using SerializedProperties = QHash >; + using SerializedKeyframes = QHash >; class LoadData { @@ -65,6 +66,8 @@ public: std::vector markers; + SerializedKeyframes keyframes; + }; class Result @@ -105,7 +108,7 @@ public: class SaveData { public: - SaveData(Project *project, const QString &filename = QString()) + SaveData(Project *project = nullptr, const QString &filename = QString()) { project_ = project; filename_ = filename; @@ -128,6 +131,9 @@ public: const std::vector &GetOnlySerializeMarkers() const { return only_serialize_markers_; } void SetOnlySerializeMarkers(const std::vector &only) { only_serialize_markers_ = only; } + const std::vector &GetOnlySerializeKeyframes() const { return only_serialize_keyframes_; } + void SetOnlySerializeKeyframes(const std::vector &only) { only_serialize_keyframes_ = only; } + const SerializedProperties &GetProperties() const { return properties_; } void SetProperties(const SerializedProperties &p) { properties_ = p; } @@ -142,6 +148,8 @@ public: std::vector only_serialize_markers_; + std::vector only_serialize_keyframes_; + }; static void Initialize(); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 6f161a07c..ae78bdf5c 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -99,6 +99,102 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } + } else if (reader->name() == QStringLiteral("keyframes")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + QString node_id; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + node_id = attr.value().toString(); + break; + } + } + + Node *n = nullptr; + if (!node_id.isEmpty()) { + n = NodeFactory::CreateFromID(node_id); + } + + if (!n) { + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("input")) { + QString input_id; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + input_id = attr.value().toString(); + break; + } + } + + if (input_id.isEmpty()) { + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + QString element_id; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + element_id = attr.value().toString(); + break; + } + } + + if (element_id.isEmpty()) { + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("track")) { + QString track_id; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + track_id = attr.value().toString(); + break; + } + } + + if (track_id.isEmpty()) { + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("key")) { + NodeKeyframe *key = new NodeKeyframe(); + key->set_input(input_id); + key->set_element(element_id.toInt()); + key->set_track(track_id.toInt()); + + LoadKeyframe(reader, key, n->GetInputDataType(input_id)); + + load_data.keyframes[node_id].append(key); + } else { + reader->skipCurrentElement(); + } + } + } + } else { + reader->skipCurrentElement(); + } + } + } + } else { + reader->skipCurrentElement(); + } + } + } + } else { + reader->skipCurrentElement(); + } + } + } + + delete n; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("markers")) { while (XMLReadNextStartElement(reader)) { @@ -221,8 +317,6 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const { - Project *project = data.GetProject(); - if (!data.GetOnlySerializeMarkers().empty()) { writer->writeStartElement(QStringLiteral("markers")); @@ -239,9 +333,63 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat writer->writeEndElement(); // markers - } else { + } else if (!data.GetOnlySerializeKeyframes().empty()) { - writer->writeTextElement(QStringLiteral("uuid"), data.GetProject()->GetUuid().toString()); + writer->writeStartElement(QStringLiteral("keyframes")); + + // Organize keyframes into node+input + QHash > > > > organized; + + for (auto it=data.GetOnlySerializeKeyframes().cbegin(); it!=data.GetOnlySerializeKeyframes().cend(); it++) { + NodeKeyframe *key = *it; + organized[key->parent()->id()][key->input()][key->element()][key->track()].append(key); + } + + for (auto it=organized.cbegin(); it!=organized.cend(); it++) { + writer->writeStartElement(QStringLiteral("node")); + + writer->writeAttribute(QStringLiteral("id"), it.key()); + + for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { + writer->writeStartElement(QStringLiteral("input")); + + writer->writeAttribute(QStringLiteral("id"), jt.key()); + + for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { + writer->writeStartElement(QStringLiteral("element")); + + writer->writeAttribute(QStringLiteral("id"), QString::number(kt.key())); + + for (auto lt=kt.value().cbegin(); lt!=kt.value().cend(); lt++) { + const QVector &keys = lt.value(); + + writer->writeStartElement(QStringLiteral("track")); + + writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); + + for (NodeKeyframe *key : keys) { + writer->writeStartElement(QStringLiteral("key")); + SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); + writer->writeEndElement(); // key + } + + writer->writeEndElement(); // track + } + + writer->writeEndElement(); // element + } + + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // node; + } + + writer->writeEndElement(); // keyframes + + } else if (Project *project = data.GetProject()) { + + writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); writer->writeStartElement(QStringLiteral("nodes")); @@ -310,6 +458,10 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat // Save main window project layout project->GetLayoutInfo().toXml(writer); + } else { + + qCritical() << "Cannot save nodes without project object"; + } } @@ -601,40 +753,13 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("key")) { - QString key_input; - rational key_time; - NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; - QPointF key_in_handle; - QPointF key_out_handle; + NodeKeyframe* key = new NodeKeyframe(); + key->set_input(input); + key->set_element(element); + key->set_track(track); - XMLAttributeLoop(reader, attr) { - if (IsCancelled()) { - return; - } - - if (attr.name() == QStringLiteral("input")) { - key_input = attr.value().toString(); - } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("type")) { - key_type = static_cast(attr.value().toInt()); - } else if (attr.name() == QStringLiteral("inhandlex")) { - key_in_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("inhandley")) { - key_in_handle.setY(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandlex")) { - key_out_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandley")) { - key_out_handle.setY(attr.value().toDouble()); - } - } - - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); - - NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); - key->set_bezier_control_in(key_in_handle); - key->set_bezier_control_out(key_out_handle); + LoadKeyframe(reader, key, data_type); + key->setParent(node); } else { reader->skipCurrentElement(); } @@ -695,15 +820,7 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node for (NodeKeyframe* key : track) { writer->writeStartElement(QStringLiteral("key")); - writer->writeAttribute(QStringLiteral("input"), key->input()); - writer->writeAttribute(QStringLiteral("time"), key->time().toString()); - writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); - writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); - writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); - writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); - writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); - - writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); + SaveKeyframe(writer, key, data_type); writer->writeEndElement(); // key } @@ -722,6 +839,53 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node } } +void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const +{ + QString key_input; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (IsCancelled()) { + return; + } + + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + key->set_time(rational::fromString(attr.value().toString())); + } else if (attr.name() == QStringLiteral("type")) { + key->set_type(static_cast(attr.value().toInt())); + } else if (attr.name() == QStringLiteral("inhandlex")) { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("inhandley")) { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandlex")) { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandley")) { + key_out_handle.setY(attr.value().toDouble()); + } + } + + key->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); + + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); +} + +void ProjectSerializer220403::SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const +{ + writer->writeAttribute(QStringLiteral("input"), key->input()); + writer->writeAttribute(QStringLiteral("time"), key->time().toString()); + writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); + writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); + writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); + writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); + writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); + + writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); +} + bool ProjectSerializer220403::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const { bool got_node_ptr = false; diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index 959a251d3..4612b2fce 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -86,6 +86,10 @@ private: void SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString &input, int element) const; + void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const; + + void SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const; + bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const; diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index f1da8ea99..b10220dab 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -216,4 +216,19 @@ void TimeBasedPanel::DeleteSelected() GetTimeBasedWidget()->DeleteSelected(); } +void TimeBasedPanel::CutSelected() +{ + GetTimeBasedWidget()->CopySelected(true); +} + +void TimeBasedPanel::CopySelected() +{ + GetTimeBasedWidget()->CopySelected(false); +} + +void TimeBasedPanel::Paste() +{ + GetTimeBasedWidget()->Paste(); +} + } diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 713ce85c7..dff33ccca 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -100,6 +100,12 @@ public: virtual void DeleteSelected() override; + virtual void CutSelected() override; + + virtual void CopySelected() override; + + virtual void Paste() override; + public slots: void SetTimebase(const rational& timebase); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 8cccbd416..f08f7b998 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -108,24 +108,9 @@ void TimelinePanel::ToggleLinks() timeline_widget()->ToggleLinksOnSelected(); } -void TimelinePanel::CutSelected() -{ - timeline_widget()->CopySelected(true); -} - -void TimelinePanel::CopySelected() -{ - timeline_widget()->CopySelected(false); -} - -void TimelinePanel::Paste() -{ - timeline_widget()->Paste(false); -} - void TimelinePanel::PasteInsert() { - timeline_widget()->Paste(true); + timeline_widget()->PasteInsert(); } void TimelinePanel::DeleteInToOut() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 06f4dc114..90de35332 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -68,12 +68,6 @@ public: virtual void ToggleLinks() override; - virtual void CutSelected() override; - - virtual void CopySelected() override; - - virtual void Paste() override; - virtual void PasteInsert() override; virtual void DeleteInToOut() override; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 44ba36a95..a01598548 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -41,6 +41,11 @@ public: void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); + const QHash &GetConnections() const + { + return track_connections_; + } + public slots: void ZoomToFit(); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 25d333d90..d58bde7cd 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -129,6 +129,36 @@ void CurveWidget::DeleteSelected() view_->DeleteSelected(); } +Node *CurveWidget::GetSelectedNodeWithID(const QString &id) +{ + for (auto it=view_->GetConnections().cbegin(); it!=view_->GetConnections().cend(); it++) { + Node *n = it.key().input().node(); + if (n->id() == id) { + return n; + } + } + + return nullptr; +} + +bool CurveWidget::CopySelected(bool cut) +{ + if (super::CopySelected(cut)) { + return true; + } + + return view_->CopySelected(cut); +} + +bool CurveWidget::Paste() +{ + if (super::Paste()) { + return true; + } + + return view_->Paste(std::bind(&CurveWidget::GetSelectedNodeWithID, this, std::placeholders::_1)); +} + void CurveWidget::SetNodes(const QVector &nodes) { tree_view_->SetNodes(nodes); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index fd2e7af25..5fae1b4df 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -55,6 +55,12 @@ public: view_->DeselectAll(); } + Node *GetSelectedNodeWithID(const QString &id); + + virtual bool CopySelected(bool cut) override; + + virtual bool Paste() override; + public slots: void SetNodes(const QVector &nodes); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 2c944f5fc..fba3b6299 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -28,6 +28,7 @@ #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" +#include "node/project/serializer/serializer.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeparamview/nodeparamviewundo.h" @@ -181,6 +182,68 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj) emit SelectionChanged(); } +bool KeyframeView::CopySelected(bool cut) +{ + if (!selection_manager_.GetSelectedObjects().empty()) { + ProjectSerializer::SaveData sdata; + sdata.SetOnlySerializeKeyframes(selection_manager_.GetSelectedObjects()); + + ProjectSerializer::Copy(sdata, QStringLiteral("keyframes")); + + if (cut) { + DeleteSelected(); + } + + return true; + } + + return false; +} + +bool KeyframeView::Paste(std::function find_node_function) +{ + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("keyframes")); + if (res == ProjectSerializer::kSuccess) { + const ProjectSerializer::SerializedKeyframes &keys = res.GetLoadData().keyframes; + + MultiUndoCommand *command = new MultiUndoCommand(); + + rational min = RATIONAL_MAX; + for (auto it=keys.cbegin(); it!=keys.cend(); it++) { + for (NodeKeyframe *key : it.value()) { + min = std::min(min, key->time()); + } + } + min -= GetTime(); + + for (auto it=keys.cbegin(); it!=keys.cend(); it++) { + const QString &paste_id = it.key(); + + // Find a node with this ID + Node *node_with_id = find_node_function(paste_id); + + if (node_with_id) { + for (NodeKeyframe *key : it.value()) { + key->set_time(key->time() - min); + + if (NodeKeyframe *existing = node_with_id->GetKeyframeAtTimeOnTrack(key->input(), key->time(), key->track(), key->element())) { + command->add_child(new NodeParamRemoveKeyframeCommand(existing)); + } + + command->add_child(new NodeParamInsertKeyframeCommand(node_with_id, key)); + } + } else { + qDeleteAll(it.value()); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + return true; + } + + return false; +} + void KeyframeView::mousePressEvent(QMouseEvent *event) { NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos()); diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 6388d5e44..2558f8cc7 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -21,6 +21,8 @@ #ifndef KEYFRAMEVIEWBASE_H #define KEYFRAMEVIEWBASE_H +#include + #include "keyframeviewinputconnection.h" #include "node/keyframe.h" #include "widget/menu/menu.h" @@ -77,6 +79,10 @@ public: UpdateSceneRect(); } + bool CopySelected(bool cut); + + bool Paste(std::function find_node_function); + signals: void Dragged(int current_x, int current_y); diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index 4c9d0be26..766879b93 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -60,6 +60,11 @@ public: return brush_; } + const NodeKeyframeTrackReference &GetReference() const + { + return input_; + } + void SetBrush(const QBrush &brush); signals: diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 051f21b55..f56cdddd5 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -28,6 +28,8 @@ #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" +#include "node/project/serializer/serializer.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timeruler/timeruler.h" @@ -519,6 +521,84 @@ void NodeParamView::SetSelectedNodes(const QVector &nodes, bo } } +Node *NodeParamView::GetNodeWithID(const QString &id) +{ + for (NodeParamViewItem *item : selected_nodes_) { + if (item->GetNode()->id() == id) { + return item->GetNode(); + } + } + + for (NodeParamViewContext *ctx : context_items_) { + for (NodeParamViewItem *item : ctx->GetItems()) { + if (item->GetNode()->id() == id) { + return item->GetNode(); + } + } + } + + return nullptr; +} + +bool NodeParamView::CopySelected(bool cut) +{ + if (super::CopySelected(cut)) { + return true; + } + + if (keyframe_view_ && keyframe_view_->hasFocus()) { + if (keyframe_view_->CopySelected(cut)) { + return true; + } + } + + if (contexts_.empty()) { + return false; + } + + ProjectSerializer::SaveData sdata(contexts_.first()->project()); + ProjectSerializer::SerializedProperties properties; + QVector nodes; + + for (NodeParamViewItem *item : selected_nodes_) { + Node *n = item->GetNode(); + + if (!nodes.contains(n)) { + nodes.append(n); + + Node::Position pos = item->GetContext()->GetNodePositionDataInContext(n); + + properties[n][QStringLiteral("x")] = QString::number(pos.position.x()); + properties[n][QStringLiteral("y")] = QString::number(pos.position.y()); + properties[n][QStringLiteral("expanded")] = QString::number(pos.expanded); + } + } + + sdata.SetOnlySerializeNodesAndResolveGroups(nodes); + sdata.SetProperties(properties); + + ProjectSerializer::Copy(sdata, QStringLiteral("nodes")); + + if (cut) { + DeleteSelected(); + } + + return false; +} + +bool NodeParamView::Paste() +{ + if (keyframe_view_) { + if (keyframe_view_->Paste(std::bind(&NodeParamView::GetNodeWithID, this, std::placeholders::_1))) { + return true; + } + } + + // FIXME: Pasting nodes + + return false; +} + void NodeParamView::UpdateItemTime(const rational &time) { foreach (NodeParamViewContext* item, context_items_) { @@ -764,14 +844,14 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - foreach (NodeParamViewContext *ctx, context_items_) { + for (NodeParamViewContext *ctx : context_items_) { for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { NodeParamViewItem *item = *it; Node *node = item->GetNode(); const KeyframeView::NodeConnections &connections = item->GetKeyframeConnections(); if (!connections.isEmpty()) { - foreach (const QString& input, node->inputs()) { + for (const QString& input : node->inputs()) { if (!(node->GetInputFlags(input) & kInputFlagHidden)) { int arr_sz = NodeGroup::ResolveInput(NodeInput(node, input)).GetArraySize(); @@ -787,7 +867,7 @@ void NodeParamView::UpdateElementY() int use_index = i + 1; if (use_index < input_con.size()) { const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1); - foreach (KeyframeViewInputConnection *track, ele_con) { + for (KeyframeViewInputConnection *track : ele_con) { track->SetKeyframeY(y); } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 8acb8f3a6..26bde1fc2 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -64,11 +64,17 @@ public: void SetSelectedNodes(const QVector &nodes, bool handle_focused_node = true, bool emit_signal = true); void SetSelectedNodes(const QVector &nodes, bool emit_signal = true); + Node *GetNodeWithID(const QString &id); + const QVector &GetContexts() const { return contexts_; } + virtual bool CopySelected(bool cut) override; + + virtual bool Paste() override; + public slots: void SetContexts(const QVector &contexts); diff --git a/app/widget/nodeparamview/nodeparamviewitembase.h b/app/widget/nodeparamview/nodeparamviewitembase.h index 574c851c9..ff31fa104 100644 --- a/app/widget/nodeparamview/nodeparamviewitembase.h +++ b/app/widget/nodeparamview/nodeparamviewitembase.h @@ -41,6 +41,8 @@ public: update(); } + bool IsHighlighted() const { return highlighted_; } + bool IsExpanded() const; static QString GetTitleBarTextFromNode(Node *n); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index f0a78ddbf..c5d079ede 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -860,4 +860,22 @@ void TimeBasedWidget::HideSnaps() } } +bool TimeBasedWidget::CopySelected(bool cut) +{ + if (ruler()->hasFocus() && ruler()->CopySelected(cut)) { + return true; + } + + return false; +} + +bool TimeBasedWidget::Paste() +{ + if (ruler()->hasFocus() && ruler()->PasteMarkers()) { + return true; + } + + return false; +} + } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index a0ed7a91b..82924160f 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -73,6 +73,10 @@ public: void ShowSnaps(const std::vector ×); void HideSnaps(); + virtual bool CopySelected(bool cut); + + virtual bool Paste(); + public slots: void SetTime(const rational &time); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index ff0bc0c9b..193649988 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -568,18 +568,14 @@ void TimelineWidget::ToggleLinksOnSelected() Core::instance()->undo_stack()->push(new NodeLinkManyCommand(blocks, link)); } -void TimelineWidget::CopySelected(bool cut) +bool TimelineWidget::CopySelected(bool cut) { - if (!GetConnectedNode()) { - return; + if (super::CopySelected(cut)) { + return true; } - if (ruler()->hasFocus() && ruler()->CopySelected(cut)) { - return; - } - - if (selected_blocks_.isEmpty()) { - return; + if (!GetConnectedNode() || selected_blocks_.isEmpty()) { + return false; } QVector selected_nodes; @@ -619,58 +615,24 @@ void TimelineWidget::CopySelected(bool cut) if (cut) { DeleteSelected(); } + + return true; } -void TimelineWidget::Paste(bool insert) +bool TimelineWidget::Paste() { - if (!GetConnectedNode()) { - return; + if (super::Paste()) { + return true; + } if (!GetConnectedNode()) { + return false; } - if (ruler()->hasFocus() && ruler()->PasteMarkers(insert, GetTime())) { - return; - } + return PasteInternal(false); +} - ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("timeline")); - if (res.GetLoadedNodes().isEmpty()) { - return; - } - - MultiUndoCommand *command = new MultiUndoCommand(); - - foreach (Node *n, res.GetLoadedNodes()) { - command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); - } - - rational paste_start = GetTime(); - - if (insert) { - rational paste_end = GetTime(); - - for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { - rational length = static_cast(it.key())->length(); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - - paste_end = qMax(paste_end, paste_start + in + length); - } - - if (paste_end != paste_start) { - InsertGapsAt(paste_start, paste_end - paste_start, command); - } - } - - for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { - Block *block = static_cast(it.key()); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); - - command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), - track.index(), - block, - paste_start + in)); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); +void TimelineWidget::PasteInsert() +{ + PasteInternal(true); } void TimelineWidget::DeleteInToOut(bool ripple) @@ -1629,6 +1591,56 @@ QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q return blocks_in_rect; } +bool TimelineWidget::PasteInternal(bool insert) +{ + if (!GetConnectedNode()) { + return false; + } + + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("timeline")); + if (res.GetLoadedNodes().isEmpty()) { + return false; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + foreach (Node *n, res.GetLoadedNodes()) { + command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); + } + + rational paste_start = GetTime(); + + if (insert) { + rational paste_end = GetTime(); + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + rational length = static_cast(it.key())->length(); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + + paste_end = qMax(paste_end, paste_start + in + length); + } + + if (paste_end != paste_start) { + InsertGapsAt(paste_start, paste_end - paste_start, command); + } + } + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + Block *block = static_cast(it.key()); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); + + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), + track.index(), + block, + paste_start + in)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + return true; +} + QByteArray TimelineWidget::SaveSplitterState() const { return view_splitter_->saveState(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 86ca06bb6..99d3e7f1f 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -79,9 +79,11 @@ public: void ToggleLinksOnSelected(); - void CopySelected(bool cut); + virtual bool CopySelected(bool cut) override; - void Paste(bool insert); + virtual bool Paste() override; + + void PasteInsert(); void DeleteInToOut(bool ripple); @@ -294,6 +296,8 @@ private: QVector GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2); + bool PasteInternal(bool insert); + QPoint drag_origin_; QRubberBand rubberband_; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index f843d6b92..c56c73330 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -102,7 +102,7 @@ void SeekableWidget::DeleteSelected() bool SeekableWidget::CopySelected(bool cut) { if (!selection_manager_.GetSelectedObjects().empty()) { - ProjectSerializer::SaveData sdata(Project::GetProjectFromObject(timeline_points_)); + ProjectSerializer::SaveData sdata; sdata.SetOnlySerializeMarkers(selection_manager_.GetSelectedObjects()); ProjectSerializer::Copy(sdata, QStringLiteral("markers")); @@ -117,7 +117,7 @@ bool SeekableWidget::CopySelected(bool cut) } } -bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) +bool SeekableWidget::PasteMarkers() { ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("markers")); if (res == ProjectSerializer::kSuccess) { @@ -128,30 +128,19 @@ bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) // Normalize markers to start at playhead rational min = RATIONAL_MAX; for (auto it=markers.cbegin(); it!=markers.cend(); it++) { - min = qMin(min, (*it)->time()); + min = std::min(min, (*it)->time()); } min -= GetTime(); - // Avoid duplicates - bool loop; - do { - loop = false; - for (auto it=markers.cbegin(); it!=markers.cend(); it++) { - rational proposed_time = (*it)->time() - min; - - if (timeline_points_->markers()->GetMarkerAtTime(proposed_time)) { - min -= timebase(); - loop = true; - break; - } - } - } while (loop); - for (auto it=markers.cbegin(); it!=markers.cend(); it++) { TimelineMarker *m = *it; m->set_time(m->time() - min); + if (TimelineMarker *existing = timeline_points_->markers()->GetMarkerAtTime(m->time())) { + command->add_child(new MarkerRemoveCommand(existing)); + } + command->add_child(new MarkerAddCommand(timeline_points_->markers(), m)); } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 47e552183..dc28e9dbe 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -54,7 +54,7 @@ public: bool CopySelected(bool cut); - bool PasteMarkers(bool insert, rational insert_time); + bool PasteMarkers(); void DeselectAllMarkers(); From 790a483e1bc3b3323a1c0f2894c8bbd10d39308c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 16:08:51 -0700 Subject: [PATCH 14/62] speeddurationdialog: implemented rippling trailing clips --- app/dialog/speedduration/speeddurationdialog.cpp | 15 +++++++-------- app/widget/timelinewidget/timelinewidget.cpp | 2 +- .../timelinewidget/undo/timelineundoripple.h | 6 ++++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp index 671bcfc25..580da35d8 100644 --- a/app/dialog/speedduration/speeddurationdialog.cpp +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -145,17 +145,11 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons void SpeedDurationDialog::accept() { - // We haven't implemented rippling yet, so warn the user - if (ripple_box_->isChecked()) { - // FIXME: Stub - if (QMessageBox::information(this, QString(), tr("Rippling is a stub and will not do anything. Do you wish to continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - return; - } - } - MultiUndoCommand *command = new MultiUndoCommand(); // Set duration values + TimelineRippleDeleteGapsAtRegionsCommand::RangeList ripple_ranges; + foreach (ClipBlock *c, clips_) { rational proposed_length = c->length(); @@ -179,10 +173,15 @@ void SpeedDurationDialog::accept() if (proposed_length != c->length()) { command->add_child(new BlockTrimCommand(c->track(), c, proposed_length, Timeline::kTrimOut)); + ripple_ranges.append({c->track(), TimeRange(c->in() + proposed_length, c->out())}); } } } + if (ripple_box_->isChecked()) { + command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(clips_.first()->track()->sequence(), ripple_ranges)); + } + // Set speed values if (speed_slider_->IsTristate()) { if (link_box_->isChecked() && !dur_slider_->IsTristate()) { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 193649988..04ec0876e 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -487,7 +487,7 @@ void TimelineWidget::DeleteSelected(bool ripple) TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr; rational new_playhead = RATIONAL_MAX; if (ripple) { - QVector > range_list; + TimelineRippleDeleteGapsAtRegionsCommand::RangeList range_list; foreach (Block* b, blocks_to_delete) { range_list.append({b->track(), b->range()}); diff --git a/app/widget/timelinewidget/undo/timelineundoripple.h b/app/widget/timelinewidget/undo/timelineundoripple.h index b5cd4b8b5..6a8c1191a 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.h +++ b/app/widget/timelinewidget/undo/timelineundoripple.h @@ -207,7 +207,9 @@ private: class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand { public: - TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector >& regions) : + using RangeList = QVector >; + + TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const RangeList& regions) : timeline_(vo), regions_(regions) { @@ -237,7 +239,7 @@ protected: private: Sequence* timeline_; - QVector > regions_; + RangeList regions_; QVector commands_; From e6bbe08ba59f6e8032d883bded9979cc6604a9d2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 17:19:59 -0700 Subject: [PATCH 15/62] slider: return cursor if wrapped --- app/widget/slider/base/sliderladder.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/widget/slider/base/sliderladder.cpp b/app/widget/slider/base/sliderladder.cpp index d1c7bdae2..245c32ab9 100644 --- a/app/widget/slider/base/sliderladder.cpp +++ b/app/widget/slider/base/sliderladder.cpp @@ -97,7 +97,10 @@ SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QString SliderLadder::~SliderLadder() { if (UsingLadders()) { - + if (wrap_count_ != 0) { + // If wrapped, restore cursor to ladder + QCursor::setPos(pos() + rect().center()); + } } else { #if defined(Q_OS_MAC) CGAssociateMouseAndMouseCursorPosition(true); From 8b87e376a028625b8a50bb3f695b148532d169e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 17:20:22 -0700 Subject: [PATCH 16/62] nodeparamview: implement copy/pasting values --- app/node/node.cpp | 112 ++++++++++++++---- app/node/node.h | 35 +++++- app/widget/nodeparamview/nodeparamview.cpp | 78 +++++++++++- .../nodeparamview/nodeparamviewundo.cpp | 8 +- app/widget/nodeparamview/nodeparamviewundo.h | 40 ++++++- app/widget/nodeview/nodeviewundo.cpp | 12 -- app/widget/nodeview/nodeviewundo.h | 26 +--- 7 files changed, 241 insertions(+), 70 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index 2f774ded1..85cc3cfb5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1064,7 +1064,7 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre } // Copy values to the clone - command->add_child(new NodeCopyInputsCommand(node, copy, false)); + CopyInputs(node, copy, false, command); // Go through input connections and copy if non-item and connect if item for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) { @@ -1110,7 +1110,7 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) command->add_child(new NodeAddCommand(static_cast(node->parent()), copy)); - command->add_child(new NodeCopyInputsCommand(node, copy, true)); + CopyInputs(node, copy, true, command); const PositionMap &map = node->GetContextPositions(); for (auto it=map.cbegin(); it!=map.cend(); it++) { @@ -1359,7 +1359,7 @@ void Node::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const Vide } } -void Node::CopyInputs(const Node *source, Node *destination, bool include_connections) +void Node::CopyInputs(const Node *source, Node *destination, bool include_connections, MultiUndoCommand *command) { Q_ASSERT(source->id() == destination->id()); @@ -1369,76 +1369,119 @@ void Node::CopyInputs(const Node *source, Node *destination, bool include_connec // passthroughs correctly. Q_ASSERT(destination->HasInputWithID(input)); - CopyInput(source, destination, input, include_connections, true); + CopyInput(source, destination, input, include_connections, true, command); } - destination->SetLabel(source->GetLabel()); - destination->SetOverrideColor(source->GetOverrideColor()); + if (command) { + command->add_child(new NodeRenameCommand(destination, source->GetLabel())); + } else { + destination->SetLabel(source->GetLabel()); + } + + if (command) { + command->add_child(new NodeOverrideColorCommand(destination, source->GetOverrideColor())); + } else { + destination->SetOverrideColor(source->GetOverrideColor()); + } } -void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays) +void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command) { Q_ASSERT(src->id() == dst->id()); - CopyValuesOfElement(src, dst, input, -1); + CopyValuesOfElement(src, dst, input, -1, command); // Copy array size if (src->InputIsArray(input) && traverse_arrays) { int src_array_sz = src->InputArraySize(input); for (int i=0; iinput_connections().cbegin(); it!=src->input_connections().cend(); it++) { - ConnectEdge(it->second, NodeInput(dst, input, it->first.element())); + // Copy all connections + for (auto it=src->input_connections().cbegin(); it!=src->input_connections().cend(); it++) { + if (!traverse_arrays && it->first.element() != -1) { + continue; } - } else { - // Just copy the primary connection (at -1) - if (src->IsInputConnected(input)) { - ConnectEdge(src->GetConnectedOutput(input), NodeInput(dst, input)); + + auto conn_output = it->second; + NodeInput conn_input(dst, input, it->first.element()); + + if (command) { + command->add_child(new NodeEdgeAddCommand(conn_output, conn_input)); + } else { + ConnectEdge(conn_output, conn_input); } } } } -void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, int src_element, int dst_element) +void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, int src_element, int dst_element, MultiUndoCommand *command) { if (dst_element >= dst->GetInternalInputArraySize(input)) { qDebug() << "Ignored destination element that was out of array bounds"; return; } + NodeInput dst_input(dst, input, dst_element); + // Copy standard value - dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element); + SplitValue standard = src->GetSplitStandardValue(input, src_element); + if (command) { + command->add_child(new NodeParamSetSplitStandardValueCommand(dst_input, standard)); + } else { + dst->SetSplitStandardValue(input, standard, dst_element); + } // Copy keyframes if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) { - immediate->delete_all_keyframes(); + if (command) { + command->add_child(new ImmediateRemoveAllKeyframesCommand(immediate)); + } else { + immediate->delete_all_keyframes(); + } } + foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { - key->copy(dst_element, dst); + NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst); + if (command) { + command->add_child(new NodeParamInsertKeyframeCommand(dst, copy)); + } } } // Copy keyframing state if (src->IsInputKeyframable(input)) { - dst->SetInputIsKeyframing(input, src->IsInputKeyframing(input, src_element), dst_element); + bool is_keying = src->IsInputKeyframing(input, src_element); + if (command) { + command->add_child(new NodeParamSetKeyframingCommand(dst_input, is_keying)); + } else { + dst->SetInputIsKeyframing(input, is_keying, dst_element); + } } // If this is the root of an array, copy the array size if (src_element == -1 && dst_element == -1) { - dst->ArrayResizeInternal(input, src->InputArraySize(input)); + int array_sz = src->InputArraySize(input); + if (command) { + command->add_child(new Node::ArrayResizeCommand(dst, input, array_sz)); + } else { + dst->ArrayResizeInternal(input, array_sz); + } } // Copy value hint - dst->SetValueHintForInput(input, src->GetValueHintForInput(input, src_element), dst_element); + Node::ValueHint vh = src->GetValueHintForInput(input, src_element); + if (command) { + command->add_child(new NodeSetValueHintCommand(dst_input, vh)); + } else { + dst->SetValueHintForInput(input, vh, dst_element); + } } bool Node::CanBeDeleted() const @@ -2151,4 +2194,25 @@ void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *no } } +void Node::ImmediateRemoveAllKeyframesCommand::prepare() +{ + for (const NodeKeyframeTrack& track : immediate_->keyframe_tracks()) { + keys_.append(track); + } +} + +void Node::ImmediateRemoveAllKeyframesCommand::redo() +{ + for (auto it=keys_.cbegin(); it!=keys_.cend(); it++) { + (*it)->setParent(&memory_manager_); + } +} + +void Node::ImmediateRemoveAllKeyframesCommand::undo() +{ + for (auto it=keys_.crbegin(); it!=keys_.crend(); it++) { + (*it)->setParent(&memory_manager_); + } +} + } diff --git a/app/node/node.h b/app/node/node.h index b7f60b838..74ef7a484 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -834,14 +834,14 @@ public: * * Nodes must be of the same types (i.e. have the same ID) */ - static void CopyInputs(const Node *source, Node* destination, bool include_connections = true); + static void CopyInputs(const Node *source, Node* destination, bool include_connections = true, MultiUndoCommand *command = nullptr); - static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays); + static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command); - static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element); - static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element) + static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element, MultiUndoCommand *command = nullptr); + static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element, MultiUndoCommand *command = nullptr) { - return CopyValuesOfElement(src, dst, input, element, element); + return CopyValuesOfElement(src, dst, input, element, element, command); } /** @@ -1265,6 +1265,31 @@ private: int array_size; }; + class ImmediateRemoveAllKeyframesCommand : public UndoCommand + { + public: + ImmediateRemoveAllKeyframesCommand(NodeInputImmediate *immediate) : + immediate_(immediate) + {} + + virtual Project* GetRelevantProject() const override { return nullptr; } + + protected: + virtual void prepare() override; + + virtual void redo() override; + + virtual void undo() override; + + private: + NodeInputImmediate *immediate_; + + QObject memory_manager_; + + QVector keys_; + + }; + NodeInputImmediate* CreateImmediate(const QString& input); NodeInputImmediate* GetImmediate(const QString& input, int element) const; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index f56cdddd5..c902503c6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -21,6 +21,7 @@ #include "nodeparamview.h" #include +#include #include #include #include @@ -594,9 +595,82 @@ bool NodeParamView::Paste() } } - // FIXME: Pasting nodes + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("nodes")); + if (res.GetLoadedNodes().isEmpty()) { + return false; + } - return false; + // Determine if any nodes of this type are already in the editor + QMap existing_nodes; + for (Node *n : res.GetLoadedNodes()) { + if (Node *existing = GetNodeWithID(n->id())) { + if (!existing_nodes.contains(existing)) { + existing_nodes.insert(existing, n); + } + } + } + + QVector nodes_to_paste_as_new = res.GetLoadedNodes(); + MultiUndoCommand *command = new MultiUndoCommand(); + + if (!existing_nodes.empty()) { + QMessageBox b(this); + b.setWindowTitle(tr("Paste Nodes")); + + QStringList node_names; + for (auto it=existing_nodes.cbegin(); it!=existing_nodes.cend(); it++) { + node_names.append(it.key()->GetLabelAndName()); + } + + b.setText(tr("The following node types already exist in this context:\n\n" + "%1\n\n" + "Do you wish to paste values onto the existing nodes or paste new nodes?").arg(node_names.join('\n'))); + + auto as_vals = b.addButton(tr("Paste As Values"), QMessageBox::YesRole); + auto as_nodes = b.addButton(tr("Paste As Nodes"), QMessageBox::NoRole); + auto cancel_btn = b.addButton(QMessageBox::Cancel); + + Q_UNUSED(as_nodes) + + b.exec(); + + if (b.clickedButton() == cancel_btn) { + + // Delete pasted nodes and clear array so no later code runs + qDeleteAll(nodes_to_paste_as_new); + nodes_to_paste_as_new.clear(); + + } else if (b.clickedButton() == as_vals) { + + // Filter out existing nodes + for (auto it=existing_nodes.cbegin(); it!=existing_nodes.cend(); it++) { + Node::CopyInputs(it.value(), it.key(), false, command); + nodes_to_paste_as_new.removeOne(it.value()); + } + + } + } + + if (!nodes_to_paste_as_new.isEmpty()) { + Node::PositionMap map; + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + if (nodes_to_paste_as_new.contains(it.key())) { + Node::Position pos; + + const QMap &node_props = it.value(); + pos.position.setX(node_props.value(QStringLiteral("x")).toDouble()); + pos.position.setY(node_props.value(QStringLiteral("y")).toDouble()); + pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble(); + + map.insert(it.key(), pos); + } + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + return true; } void NodeParamView::UpdateItemTime(const rational &time) diff --git a/app/widget/nodeparamview/nodeparamviewundo.cpp b/app/widget/nodeparamview/nodeparamviewundo.cpp index 427c1eacb..a3e448fda 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.cpp +++ b/app/widget/nodeparamview/nodeparamviewundo.cpp @@ -27,9 +27,8 @@ namespace olive { NodeParamSetKeyframingCommand::NodeParamSetKeyframingCommand(const NodeInput &input, bool setting) : input_(input), - setting_(setting) + new_setting_(setting) { - Q_ASSERT(setting != input_.IsKeyframing()); } Project *NodeParamSetKeyframingCommand::GetRelevantProject() const @@ -39,12 +38,13 @@ Project *NodeParamSetKeyframingCommand::GetRelevantProject() const void NodeParamSetKeyframingCommand::redo() { - input_.node()->SetInputIsKeyframing(input_, setting_); + old_setting_ = input_.IsKeyframing(); + input_.node()->SetInputIsKeyframing(input_, new_setting_); } void NodeParamSetKeyframingCommand::undo() { - input_.node()->SetInputIsKeyframing(input_, !setting_); + input_.node()->SetInputIsKeyframing(input_, old_setting_); } NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant& value) : diff --git a/app/widget/nodeparamview/nodeparamviewundo.h b/app/widget/nodeparamview/nodeparamviewundo.h index 80a6e4e41..90cdce452 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.h +++ b/app/widget/nodeparamview/nodeparamviewundo.h @@ -41,7 +41,8 @@ protected: private: NodeInput input_; - bool setting_; + bool new_setting_; + bool old_setting_; }; @@ -145,6 +146,43 @@ private: }; +class NodeParamSetSplitStandardValueCommand : public UndoCommand +{ +public: + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& new_value, const SplitValue& old_value) : + ref_(input), + old_value_(old_value), + new_value_(new_value) + {} + + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& value) : + NodeParamSetSplitStandardValueCommand(input, value, input.node()->GetSplitStandardValue(input.input())) + {} + + virtual Project* GetRelevantProject() const override + { + return ref_.node()->project(); + } + +protected: + virtual void redo() override + { + ref_.node()->SetSplitStandardValue(ref_.input(), new_value_, ref_.element()); + } + + virtual void undo() override + { + ref_.node()->SetSplitStandardValue(ref_.input(), old_value_, ref_.element()); + } + +private: + NodeInput ref_; + + SplitValue old_value_; + SplitValue new_value_; + +}; + class NodeParamArrayAppendCommand : public UndoCommand { public: diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index fc9d9073f..03ff5a372 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -112,18 +112,6 @@ Project *NodeAddCommand::GetRelevantProject() const return dynamic_cast(graph_); } -NodeCopyInputsCommand::NodeCopyInputsCommand(const Node *src, Node *dest, bool include_connections) : - src_(src), - dest_(dest), - include_connections_(include_connections) -{ -} - -void NodeCopyInputsCommand::redo() -{ - Node::CopyInputs(src_, dest_, include_connections_); -} - void NodeRemoveAndDisconnectCommand::prepare() { command_ = new MultiUndoCommand(); diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 3c2298cd5..f5508abb2 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -193,28 +193,6 @@ private: }; -class NodeCopyInputsCommand : public UndoCommand { -public: - NodeCopyInputsCommand(const Node* src, - Node* dest, - bool include_connections); - - virtual Project* GetRelevantProject() const override {return nullptr;} - -protected: - virtual void redo() override; - - virtual void undo() override {} - -private: - const Node* src_; - - Node* dest_; - - bool include_connections_; - -}; - class NodeLinkCommand : public UndoCommand { public: NodeLinkCommand(Node* a, Node* b, bool link) : @@ -324,6 +302,10 @@ class NodeRenameCommand : public UndoCommand { public: NodeRenameCommand() = default; + NodeRenameCommand(Node* node, const QString& new_name) + { + AddNode(node, new_name); + } void AddNode(Node* node, const QString& new_name); From fc8bba956b57633c362d476622ee5a8b50aef703 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 17:32:48 -0700 Subject: [PATCH 17/62] nodeparamview: support pasting values into multiple nodes of same type --- app/widget/nodeparamview/nodeparamview.cpp | 17 +++++++++++------ app/widget/nodeparamview/nodeparamview.h | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index c902503c6..e687ada6a 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -523,16 +523,21 @@ void NodeParamView::SetSelectedNodes(const QVector &nodes, bo } Node *NodeParamView::GetNodeWithID(const QString &id) +{ + return GetNodeWithIDAndIgnoreList(id, QVector()); +} + +Node *NodeParamView::GetNodeWithIDAndIgnoreList(const QString &id, const QVector &ignore) { for (NodeParamViewItem *item : selected_nodes_) { - if (item->GetNode()->id() == id) { + if (item->GetNode()->id() == id && !ignore.contains(item->GetNode())) { return item->GetNode(); } } for (NodeParamViewContext *ctx : context_items_) { for (NodeParamViewItem *item : ctx->GetItems()) { - if (item->GetNode()->id() == id) { + if (item->GetNode()->id() == id && !ignore.contains(item->GetNode())) { return item->GetNode(); } } @@ -601,12 +606,12 @@ bool NodeParamView::Paste() } // Determine if any nodes of this type are already in the editor + QVector ignore_nodes; QMap existing_nodes; for (Node *n : res.GetLoadedNodes()) { - if (Node *existing = GetNodeWithID(n->id())) { - if (!existing_nodes.contains(existing)) { - existing_nodes.insert(existing, n); - } + if (Node *existing = GetNodeWithIDAndIgnoreList(n->id(), ignore_nodes)) { + existing_nodes.insert(existing, n); + ignore_nodes.append(existing); } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 26bde1fc2..534ae0964 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -65,6 +65,7 @@ public: void SetSelectedNodes(const QVector &nodes, bool emit_signal = true); Node *GetNodeWithID(const QString &id); + Node *GetNodeWithIDAndIgnoreList(const QString &id, const QVector &ignore); const QVector &GetContexts() const { From 67ac32ee5702036d99995fa653e0b0ce3cc0efe3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 17:44:26 -0700 Subject: [PATCH 18/62] viewertexteditor: don't paste rich text --- app/widget/viewer/viewertexteditor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index cb2bd685f..76fd94037 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -64,6 +64,8 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : connect(qApp, &QApplication::focusChanged, this, &ViewerTextEditor::FocusChanged); connect(this, &QTextEdit::currentCharFormatChanged, this, &ViewerTextEditor::FormatChanged); connect(document(), &QTextDocument::contentsChanged, this, &ViewerTextEditor::DocumentChanged, Qt::QueuedConnection); + + setAcceptRichText(false); } void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar) From 501564fb2228b3d6d263dbf0a9cd6603625f8ac5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 18:07:17 -0700 Subject: [PATCH 19/62] ui: replace html edit in text node with edit in viewer button --- app/node/generator/text/textv3.cpp | 1 + app/panel/param/param.cpp | 1 + app/panel/param/param.h | 2 + app/panel/viewer/viewerbase.h | 5 + app/widget/nodeparamview/nodeparamview.cpp | 10 ++ app/widget/nodeparamview/nodeparamview.h | 4 + .../nodeparamview/nodeparamviewitem.cpp | 2 + app/widget/nodeparamview/nodeparamviewitem.h | 4 + .../nodeparamview/nodeparamviewtextedit.cpp | 24 +++- .../nodeparamview/nodeparamviewtextedit.h | 9 ++ .../nodeparamviewwidgetbridge.cpp | 10 ++ .../nodeparamview/nodeparamviewwidgetbridge.h | 2 + app/widget/viewer/viewer.h | 5 + app/widget/viewer/viewerdisplay.cpp | 117 +++++++++++------- app/widget/viewer/viewerdisplay.h | 5 + app/window/mainwindow/mainwindow.cpp | 1 + 16 files changed, 148 insertions(+), 54 deletions(-) diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index 76f2623e6..da6f9bfe8 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -44,6 +44,7 @@ TextGeneratorV3::TextGeneratorV3() : ShapeNodeBase(false) { AddInput(kTextInput, NodeValue::kText, QStringLiteral("

%1

").arg(tr("Sample Text"))); + SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true); SetStandardValue(kSizeInput, QVector2D(400, 300)); diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 3e494174d..48fb8dfdb 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -30,6 +30,7 @@ ParamPanel::ParamPanel(QWidget* parent) : NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged); connect(view, &NodeParamView::SelectedNodesChanged, this, &ParamPanel::SelectedNodesChanged); + connect(view, &NodeParamView::RequestViewerToStartEditingText, this, &ParamPanel::RequestViewerToStartEditingText); connect(this, &ParamPanel::visibilityChanged, view, &NodeParamView::UpdateElementY); SetTimeBasedWidget(view); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index e07fed1f7..5b55075ef 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -67,6 +67,8 @@ signals: void SelectedNodesChanged(const QVector &nodes); + void RequestViewerToStartEditingText(); + protected: virtual void Retranslate() override; diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index b5319ca55..e5fe7cdff 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -69,6 +69,11 @@ public slots: void CacheSequenceInOut(); + void RequestStartEditingText() + { + static_cast(GetTimeBasedWidget())->RequestStartEditingText(); + } + signals: /** * @brief Signal emitted when a new frame is loaded diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index e687ada6a..99a4cd95f 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -320,6 +320,15 @@ void NodeParamView::SelectNodeFromConnectedLink(Node *node) SetSelectedNodes({p}); } +void NodeParamView::RequestEditTextInViewer() +{ + NodeParamViewItem *item = static_cast(sender()); + + focused_node_ = item; + emit FocusedNodeChanged(item->GetNode()); + emit RequestViewerToStartEditingText(); +} + void NodeParamView::SetContexts(const QVector &contexts) { // Setting contexts is expensive, so we queue it here to prevent multiple calls in a short timespan @@ -735,6 +744,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::InputCheckBoxChanged); connect(item, &NodeParamViewItem::Clicked, this, &NodeParamView::ItemClicked); + connect(item, &NodeParamViewItem::RequestEditTextInViewer, this, &NodeParamView::RequestEditTextInViewer); item->SetContext(ctx); item->SetTimeTarget(GetTimeTarget()); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 534ae0964..ba918d373 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -86,6 +86,8 @@ signals: void SelectedNodesChanged(const QVector &nodes); + void RequestViewerToStartEditingText(); + protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -182,6 +184,8 @@ private slots: void SelectNodeFromConnectedLink(Node *node); + void RequestEditTextInViewer(); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index b133d7c07..3151724ce 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -90,6 +90,7 @@ void NodeParamViewItem::RecreateBody() connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + connect(body_, &NodeParamViewItemBody::RequestEditTextInViewer, this, &NodeParamViewItem::RequestEditTextInViewer); body_->Retranslate(); body_->SetTime(time_); body_->SetTimebase(timebase_); @@ -237,6 +238,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const ui_objects.widget_bridge = new NodeParamViewWidgetBridge(NodeInput(node, input, element), this); connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::WidgetsRecreated, this, &NodeParamViewItemBody::ReplaceWidgets); connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked, this, &NodeParamViewItemBody::ToggleArrayExpanded); + connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::RequestEditTextInViewer, this, &NodeParamViewItemBody::RequestEditTextInViewer); // Place widgets into layout PlaceWidgetsFromBridge(layout, ui_objects.widget_bridge, row); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 581699d79..8ec20bb6a 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -73,6 +73,8 @@ signals: void InputCheckedChanged(const NodeInput &input, bool e); + void RequestEditTextInViewer(); + private: void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index); @@ -222,6 +224,8 @@ signals: void InputCheckedChanged(const NodeInput &input, bool e); + void RequestEditTextInViewer(); + protected slots: virtual void Retranslate() override; diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index 64ce8da2a..e9423f360 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -21,7 +21,6 @@ #include "nodeparamviewtextedit.h" #include -#include #include "dialog/text/text.h" #include "ui/icons/icons.h" @@ -39,11 +38,24 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : connect(line_edit_, &QPlainTextEdit::textChanged, this, &NodeParamViewTextEdit::InnerWidgetTextChanged); layout->addWidget(line_edit_); - QPushButton* edit_btn = new QPushButton(); - edit_btn->setIcon(icon::ToolEdit); - edit_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); - layout->addWidget(edit_btn); - connect(edit_btn, &QPushButton::clicked, this, &NodeParamViewTextEdit::ShowTextDialog); + edit_btn_ = new QPushButton(); + edit_btn_->setIcon(icon::ToolEdit); + edit_btn_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + layout->addWidget(edit_btn_); + connect(edit_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::ShowTextDialog); + + edit_in_viewer_btn_ = new QPushButton(tr("Edit In Viewer")); + layout->addWidget(edit_in_viewer_btn_); + connect(edit_in_viewer_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::RequestEditInViewer); + + SetEditInViewerOnlyMode(false); +} + +void NodeParamViewTextEdit::SetEditInViewerOnlyMode(bool on) +{ + line_edit_->setVisible(!on); + edit_btn_->setVisible(!on); + edit_in_viewer_btn_->setVisible(on); } void NodeParamViewTextEdit::ShowTextDialog() diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index a61087db8..2c2577566 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -22,6 +22,7 @@ #define NODEPARAMVIEWTEXTEDIT_H #include +#include #include #include "common/define.h" @@ -39,6 +40,8 @@ public: return line_edit_->toPlainText(); } + void SetEditInViewerOnlyMode(bool on); + public slots: void setText(const QString &s) { @@ -64,9 +67,15 @@ public slots: signals: void textEdited(const QString &); + void RequestEditInViewer(); + private: QPlainTextEdit* line_edit_; + QPushButton* edit_btn_; + + QPushButton *edit_in_viewer_btn_; + private slots: void ShowTextDialog(); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index f28e46559..aff1fb770 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -141,6 +141,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(); widgets_.append(line_edit); connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); + connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer); break; } case NodeValue::kBoolean: @@ -781,6 +782,15 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & ff->SetDirectoryMode(value.toBool()); } } + + // Parameters for text + if (data_type == NodeValue::kText) { + NodeParamViewTextEdit *tex = static_cast(widgets_.first()); + + if (key == QStringLiteral("vieweronly")) { + tex->SetEditInViewerOnlyMode(value.toBool()); + } + } } void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index d7298cc2c..98a71d5d7 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -57,6 +57,8 @@ signals: void WidgetsRecreated(const NodeInput& input); + void RequestEditTextInViewer(); + private: void CreateWidgets(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 7397365b1..3a297fbc8 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -119,6 +119,11 @@ public slots: void UpdateTextureFromNode(); + void RequestStartEditingText() + { + display_widget_->RequestStartEditingText(); + } + signals: /** * @brief Wrapper for ViewerGLWidget::CursorColor() diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index b4b61f173..76c5c09e2 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -45,7 +45,6 @@ #include "node/gizmo/point.h" #include "node/gizmo/polygon.h" #include "node/gizmo/screen.h" -#include "node/gizmo/text.h" #include "node/traverser.h" #include "viewertexteditor.h" #include "window/mainwindow/mainwindow.h" @@ -353,53 +352,7 @@ void ViewerDisplayWidget::mouseDoubleClickEvent(QMouseEvent *event) foreach (NodeGizmo *g, gizmos_->GetGizmos()) { if (TextGizmo *text = dynamic_cast(g)) { if (text->GetRect().contains(ptr)) { - QTransform gizmo_transform = GenerateGizmoTransform(); - - ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); - Html::HtmlToDoc(text_edit->document(), text->GetHtml()); - text_edit->setProperty("gizmo", reinterpret_cast(text)); - - QRectF transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect(); - text_edit->setGeometry(transformed_geom.toRect()); - - ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); - - QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height())); - for (QScreen *screen : qApp->screens()) { - if (screen->geometry().contains(pos)) { - if (pos.x() + toolbar->width() > screen->geometry().right()) { - pos.setX(screen->geometry().right() - toolbar->width()); - } - break; - } - } - toolbar->move(pos); - toolbar->show(); - - text_edit->show(); - - connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); - - text_edit->ConnectToolBar(toolbar); - - QPoint text_edit_pos = text_edit->mapFrom(this, event->pos()); - - // Ensure text edit is actually focused rather than the toolbar - connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [this, text_edit, text_edit_pos]{ - // Grab focus back from the toolbar - this->raise(); - this->activateWindow(); - text_edit->setFocus(); - - // Start text cursor where the user clicked - text_edit->setTextCursor(text_edit->cursorForPosition(text_edit_pos)); - - // HACK: On macOS, for some reason the QDockWidget receives focus before the - // ViewerTextEditor, causing the editor to close prematurely. However this only - // happens the first time the editor receives focus and not subsequent times, so - // if we get it to only listen after the first one, this solves the problem. - text_edit->SetListenToFocusEvents(true); - }); + OpenTextGizmo(text, event); break; } } @@ -773,6 +726,62 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo return nullptr; } +void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) +{ + QTransform gizmo_transform = GenerateGizmoTransform(); + + ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); + Html::HtmlToDoc(text_edit->document(), text->GetHtml()); + text_edit->setProperty("gizmo", reinterpret_cast(text)); + + QRectF transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect(); + text_edit->setGeometry(transformed_geom.toRect()); + + ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); + + QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height())); + for (QScreen *screen : qApp->screens()) { + if (screen->geometry().contains(pos)) { + if (pos.x() + toolbar->width() > screen->geometry().right()) { + pos.setX(screen->geometry().right() - toolbar->width()); + } + break; + } + } + toolbar->move(pos); + toolbar->show(); + + text_edit->show(); + + connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); + + text_edit->ConnectToolBar(toolbar); + + QPoint text_edit_pos; + if (event) { + text_edit_pos = text_edit->mapFrom(this, event->pos()); + } + + // Ensure text edit is actually focused rather than the toolbar + connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [this, text_edit, text_edit_pos]{ + // Grab focus back from the toolbar + this->raise(); + this->activateWindow(); + text_edit->setFocus(); + + // Start text cursor where the user clicked + if (!text_edit_pos.isNull()) { + text_edit->setTextCursor(text_edit->cursorForPosition(text_edit_pos)); + } + + // HACK: On macOS, for some reason the QDockWidget receives focus before the + // ViewerTextEditor, causing the editor to close prematurely. However this only + // happens the first time the editor receives focus and not subsequent times, so + // if we get it to only listen after the first one, this solves the problem. + text_edit->SetListenToFocusEvents(true); + }); +} + void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) { // Do this no matter what, emits signal to any pixel samplers @@ -798,6 +807,18 @@ void ViewerDisplayWidget::SetShowFPS(bool e) update(); } +void ViewerDisplayWidget::RequestStartEditingText() +{ + if (gizmos_) { + foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { + if (TextGizmo *text = dynamic_cast(gizmo)) { + OpenTextGizmo(text); + break; + } + } + } +} + void ViewerDisplayWidget::Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase) { playback_timebase_ = timebase; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index d66d25ad7..a8c993aa1 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -25,6 +25,7 @@ #include #include "node/color/colormanager/colormanager.h" +#include "node/gizmo/text.h" #include "node/node.h" #include "node/output/track/tracklist.h" #include "render/color.h" @@ -169,6 +170,8 @@ public slots: void SetShowFPS(bool e); + void RequestStartEditingText(); + signals: /** * @brief Signal emitted when the user starts dragging from the viewer @@ -264,6 +267,8 @@ private: NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p); + void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index b6b3fb0e0..176575e40 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -96,6 +96,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(node_panel_, &NodePanel::NodeGroupOpened, this, &MainWindow::NodePanelGroupOpenedOrClosed); connect(node_panel_, &NodePanel::NodeGroupClosed, this, &MainWindow::NodePanelGroupOpenedOrClosed); connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); + connect(param_panel_, &ParamPanel::RequestViewerToStartEditingText, sequence_viewer_panel_, &ViewerPanel::RequestStartEditingText); connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode); connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select); From dbb5430c0ecc2d2bb3ac654f391ae291c7cecd79 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 08:20:57 -0700 Subject: [PATCH 20/62] track: fixed bug when pasting subtitle clips --- app/node/output/track/track.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 7a552a018..8c1c02d79 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -222,6 +222,9 @@ public: } else if (s.at(0) == 'a') { // Audio stream return Track::kAudio; + } else if (s.at(0) == 's') { + // Subtitle stream + return Track::kSubtitle; } } } From 9969a29be1345c09b7020328867648c42926964c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 08:51:51 -0700 Subject: [PATCH 21/62] ci: include QtNetwork in dylibbundler call --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03343fd3a..08f2dea0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -376,17 +376,18 @@ jobs: if [ "${{ matrix.os-arch }}" == "x86_64" ] then - DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler" + DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler -x $BUNDLE_NAME/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork" fi mv app/$BUNDLE_NAME . mkdir $BUNDLE_NAME/Contents/Frameworks - dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS # Copy Qt frameworks and plugins cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents + dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS + # HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64, # but perhaps I'll bring it up with them soon. cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib From 92dd49961db575391fd3bac39f538b54db15b73e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 09:22:53 -0700 Subject: [PATCH 22/62] core: change manual crash trigger --- app/core.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index ce3fa7e72..1b85ae09a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -185,9 +185,7 @@ void Core::Start() QTimer *crash_timer = new QTimer(this); crash_timer->setInterval(interval); connect(crash_timer, &QTimer::timeout, this, []{ - // Try to read invalid memory to crash the application - int *invalid_ptr = nullptr; - qDebug() << *invalid_ptr; + abort(); }); crash_timer->start(); } From e9ebef1de838392411c4ac9e59f4233b05fc9db8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 09:23:02 -0700 Subject: [PATCH 23/62] ci: enable crashpad on mac arm64 --- .github/workflows/ci.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08f2dea0b..992be23ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -332,7 +332,6 @@ jobs: run: | $DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-${{ matrix.os-arch }}.tar.gz sudo tar xzf crashpad-mac-${{ matrix.os-arch }}.tar.gz -C / - if: matrix.os-arch == 'x86_64' # TEMPORARY: Crashpad isn't compiling on ARM64 yet for some reason - name: Generate Patreon List env: @@ -374,11 +373,6 @@ jobs: BUNDLE_NAME="Olive.app" brew install dylibbundler - if [ "${{ matrix.os-arch }}" == "x86_64" ] - then - DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler -x $BUNDLE_NAME/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork" - fi - mv app/$BUNDLE_NAME . mkdir $BUNDLE_NAME/Contents/Frameworks @@ -386,21 +380,19 @@ jobs: cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents - dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS + dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" \ + -x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler -x $BUNDLE_NAME/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork # HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64, # but perhaps I'll bring it up with them soon. cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib - if [ "${{ matrix.os-arch }}" == "x86_64" ] - then - # Crashpad symbols - $DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym - SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file - SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]} - mkdir -p "$SYM_DIR" - mv Olive.sym "$SYM_DIR" - fi + # Crashpad symbols + $DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym + SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file + SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]} + mkdir -p "$SYM_DIR" + mv Olive.sym "$SYM_DIR" - name: Sign Application working-directory: ${{ runner.workspace }}/build From 6212f675a2c19aaee12c65c77b26a1f6be380c6f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 09:32:05 -0700 Subject: [PATCH 24/62] footage: use version number on cache Will allow us to easily discard cache later --- app/node/project/footage/footage.cpp | 8 ++----- .../project/footage/footagedescription.cpp | 22 ++++++++++++++++++- app/node/project/footage/footagedescription.h | 2 ++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 1bf2dc05a..55f4043e4 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -90,12 +90,8 @@ void Footage::InputValueChangedEvent(const QString &input, int element) FootageDescription footage_info; - if (QFileInfo::exists(meta_cache_file)) { - - // Load meta cache file - footage_info.Load(meta_cache_file); - - } else { + // Try to load footage info from cache + if (!QFileInfo::exists(meta_cache_file) || !footage_info.Load(meta_cache_file)) { // Probe and create cache QVector decoder_list = Decoder::ReceiveListOfAllDecoders(); diff --git a/app/node/project/footage/footagedescription.cpp b/app/node/project/footage/footagedescription.cpp index 790f264ae..794919496 100644 --- a/app/node/project/footage/footagedescription.cpp +++ b/app/node/project/footage/footagedescription.cpp @@ -40,6 +40,20 @@ bool FootageDescription::Load(const QString &filename) while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("streamcache")) { + // Default to first version of metadata (which wasn't versioned at all) + unsigned version = 1; + + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("version")) { + version = attr.value().toUInt(); + } + } + + if (version != kFootageMetaVersion) { + // If this is a different version, discard so we can probe new data + return false; + } + while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("decoder")) { decoder_ = reader.readElementText(); @@ -68,7 +82,11 @@ bool FootageDescription::Load(const QString &filename) file.close(); - return true; + if (reader.hasError()) { + qWarning() << "Failed to load footage description for" << filename << reader.errorString(); + } else { + return true; + } } return false; @@ -88,6 +106,8 @@ bool FootageDescription::Save(const QString &filename) const writer.writeStartElement(QStringLiteral("streamcache")); + writer.writeAttribute(QStringLiteral("version"), QString::number(kFootageMetaVersion)); + writer.writeTextElement(QStringLiteral("decoder"), decoder_); writer.writeStartElement(QStringLiteral("streams")); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index b8009f6fc..88c219195 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -112,6 +112,8 @@ public: } private: + static constexpr unsigned kFootageMetaVersion = 1; + QString decoder_; QVector video_streams_; From 1ce495d299cbf65ef55d2239066e2a5127957ffc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 10:22:01 -0700 Subject: [PATCH 25/62] cmake: upgrade to C++17 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90524f566..b46d11f6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) option(BUILD_TESTS "Build unit tests" ON) option(USE_WERROR "Error on compile warning" ON) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) From 490bad990a58a260b8e88f05db7dd3e352e82009 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 10:32:02 -0700 Subject: [PATCH 26/62] crashpad: update for new OS macros --- app/common/crashpadinterface.cpp | 2 +- app/common/crashpadutils.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/common/crashpadinterface.cpp b/app/common/crashpadinterface.cpp index 70c30076f..d9b6fcb86 100644 --- a/app/common/crashpadinterface.cpp +++ b/app/common/crashpadinterface.cpp @@ -32,7 +32,7 @@ #include "crashpadutils.h" #include "filefunctions.h" -#ifdef OS_WIN +#ifdef BUILDFLAG(IS_WIN) #include #endif diff --git a/app/common/crashpadutils.h b/app/common/crashpadutils.h index 88b332a76..8b0e3e6d7 100644 --- a/app/common/crashpadutils.h +++ b/app/common/crashpadutils.h @@ -24,17 +24,17 @@ #include // Copied from base::FilePath to match its macro -#if defined(OS_POSIX) +#if BUILDFLAG(IS_POSIX) // On most platforms, native pathnames are char arrays, and the encoding // may or may not be specified. On Mac OS X, native pathnames are encoded // in UTF-8. #define QSTRING_TO_BASE_STRING(x) x.toStdString() #define BASE_STRING_TO_QSTRING(x) QString::fromStdString(x) -#elif defined(OS_WIN) +#elif BUILDFLAG(IS_WIN) // On Windows, for Unicode-aware applications, native pathnames are wchar_t // arrays encoded in UTF-16. #define QSTRING_TO_BASE_STRING(x) x.toStdWString() #define BASE_STRING_TO_QSTRING(x) QString::fromStdWString(x) -#endif // OS_WIN +#endif // BUILDFLAG(IS_WIN) #endif // CRASHPADUTILS_H From 7c12b6dc169bc6c45bb59e88ff3ed0cf780769dd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 10:50:47 -0700 Subject: [PATCH 27/62] updates for C++17 --- .../markerpropertiesdialog.cpp | 4 +- app/node/keyframe.h | 8 ---- .../project/serializer/serializer220403.cpp | 4 +- app/timeline/timelinemarker.cpp | 6 +-- app/timeline/timelinemarker.h | 20 +++------- .../resizabletimelinescrollbar.cpp | 4 +- .../timebased/timebasedviewselectionmanager.h | 38 +++++++++++++++---- app/widget/timebased/timebasedwidget.cpp | 16 ++++---- .../timelinewidget/view/timelineview.cpp | 6 +-- app/widget/timeruler/seekablewidget.cpp | 20 +++++----- 10 files changed, 67 insertions(+), 59 deletions(-) diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 36b7210fe..18fca8cfa 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -60,10 +60,10 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vectorSetValue(markers.front()->time_range().in()); + in_slider_->SetValue(markers.front()->time().in()); in_slider_->SetDisplayType(RationalSlider::kTime); in_slider_->SetTimebase(timebase); - out_slider_->SetValue(markers.front()->time_range().out()); + out_slider_->SetValue(markers.front()->time().out()); out_slider_->SetDisplayType(RationalSlider::kTime); out_slider_->SetTimebase(timebase); } else { diff --git a/app/node/keyframe.h b/app/node/keyframe.h index a002aa8fc..ffe5a7728 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -87,14 +87,6 @@ public: const rational& time() const; void set_time(const rational& time); - /** - * @brief Dummy function for TimeBasedViewSelectionManager compatibility - * - * FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in - * TimeBasedViewSelectionManager's template functions - */ - TimeRange time_range() const { return TimeRange(time_, time_); } - /** * @brief The value of this keyframe (i.e. the value to use at this keyframe's time) */ diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index ae78bdf5c..5fb2ab4ab 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -1175,8 +1175,8 @@ void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarke void ProjectSerializer220403::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const { writer->writeAttribute(QStringLiteral("name"), marker->name()); - writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString()); + writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 48611207b..d8b87b7fd 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -167,9 +167,9 @@ void TimelineMarkerList::InsertIntoList(TimelineMarker *marker) for (auto it=markers_.begin(); it!=markers_.end(); it++) { TimelineMarker *m = *it; - Q_ASSERT(m->time() != marker->time()); + Q_ASSERT(m->time().in() != marker->time().in()); - if (m->time() > marker->time()) { + if (m->time().in() > marker->time().in()) { markers_.insert(it, marker); found = true; break; @@ -316,7 +316,7 @@ Project* MarkerChangeTimeCommand::GetRelevantProject() const void MarkerChangeTimeCommand::redo() { - old_time_ = marker_->time_range(); + old_time_ = marker_->time(); marker_->set_time(new_time_); } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 671f7431b..7712111d2 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -38,17 +38,9 @@ public: TimelineMarker(QObject* parent = nullptr); TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr); - /** - * @brief Dummy function for TimeBasedViewSelectionManager compatibility - * - * FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in - * TimeBasedViewSelectionManager's template functions - */ - const rational &time() const { return time_.in(); } - void set_time(const rational& time); - - const TimeRange &time_range() const { return time_; } + const TimeRange &time() const { return time_; } void set_time(const TimeRange& time); + void set_time(const rational& time); bool has_sibling_at_time(const rational &t) const; @@ -99,7 +91,7 @@ public: { for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) { TimelineMarker *m = *it; - if (m->time() == t) { + if (m->time().in() == t) { return m; } } @@ -114,10 +106,10 @@ public: for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) { TimelineMarker *m = *it; - rational this_diff = qAbs(m->time() - t); + rational this_diff = qAbs(m->time().in() - t); if (closest) { - rational stored_diff = qAbs(closest->time() - t); + rational stored_diff = qAbs(closest->time().in() - t); if (this_diff > stored_diff) { // Since the list is organized by time, if the diff increases, assume we are only going @@ -229,7 +221,7 @@ class MarkerChangeTimeCommand : public UndoCommand { public: MarkerChangeTimeCommand(TimelineMarker* marker, const TimeRange &time, const TimeRange &old_time); MarkerChangeTimeCommand(TimelineMarker* marker, const TimeRange &time) : - MarkerChangeTimeCommand(marker, time, marker->time_range()) + MarkerChangeTimeCommand(marker, time, marker->time()) {} virtual Project* GetRelevantProject() const override; diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index e3458c09f..4672657bb 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -117,8 +117,8 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) TimelineMarker* marker = *it; QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor(); - int64_t in = qRound64(ratio * TimeToScene(marker->time_range().in())); - int64_t out = qRound64(ratio * TimeToScene(marker->time_range().out())); + int64_t in = qRound64(ratio * TimeToScene(marker->time().in())); + int64_t out = qRound64(ratio * TimeToScene(marker->time().out())); int64_t length = qMax(int64_t(1), out-in); p.fillRect(gr.x() + in, diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index f8d0df119..7647ddc06 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -163,14 +163,24 @@ public: initial_drag_item_ = initial_item; dragging_.resize(selected_.size()); - snap_points_.resize(selected_.size()*2); + + if constexpr (std::is_same_v) { + snap_points_.resize(selected_.size()*2); + } else { + snap_points_.resize(selected_.size()); + } + for (size_t i=0; itime(); - - snap_points_[i] = obj->time(); - snap_points_[i+selected_.size()] = obj->time_range().out(); + if constexpr (std::is_same_v) { + dragging_[i] = obj->time().in(); + snap_points_[i] = obj->time().in(); + snap_points_[i+selected_.size()] = obj->time().out(); + } else { + dragging_[i] = obj->time(); + snap_points_[i] = obj->time(); + } } drag_mouse_start_ = view_->mapToScene(event->pos()); @@ -258,7 +268,15 @@ public: } // Show information about this keyframe - QString tip = Timecode::time_to_timecode(initial_drag_item_->time(), timebase_, + rational display_time; + + if constexpr (std::is_same_v) { + display_time = initial_drag_item_->time().in(); + } else { + display_time = initial_drag_item_->time(); + } + + QString tip = Timecode::time_to_timecode(display_time, timebase_, Core::instance()->GetTimecodeDisplay(), false); if (!tip_format.isEmpty()) { @@ -274,7 +292,13 @@ public: QToolTip::hideText(); for (size_t i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i))); + rational current; + if constexpr (std::is_same_v) { + current = selected_.at(i)->time().in(); + } else { + current = selected_.at(i)->time(); + } + command->add_child(new SetTimeCommand(selected_.at(i), current, dragging_.at(i))); } dragging_.clear(); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index c5d079ede..a812e6c69 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -346,10 +346,10 @@ void TimeBasedWidget::GoToPrevCut() rational closest_cut = 0; - foreach (Track* track, sequence->GetTracks()) { + for (Track* track : sequence->GetTracks()) { rational this_track_closest_cut = 0; - foreach (Block* block, track->Blocks()) { + for (Block* block : track->Blocks()) { if (block->out() < GetTime()) { this_track_closest_cut = block->out(); } else { @@ -754,7 +754,7 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration for (auto jt=markers->cbegin(); jt!=markers->cend(); jt++) { TimelineMarker *marker = *jt; - TimeRange marker_range = marker->time_range() + clip->in() - clip->media_in(); + TimeRange marker_range = marker->time() + clip->in() - clip->media_in(); qreal marker_in_screen = TimeToScene(marker_range.in()); qreal marker_out_screen = TimeToScene(marker_range.out()); @@ -777,12 +777,12 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration continue; } - qreal marker_pos = TimeToScene(m->time_range().in()); - AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().in()); + qreal marker_pos = TimeToScene(m->time().in()); + AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time().in()); - if (m->time_range().in() != m->time_range().out()) { - marker_pos = TimeToScene(m->time_range().out()); - AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().out()); + if (m->time().in() != m->time().out()) { + marker_pos = TimeToScene(m->time().out()); + AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time().out()); } } } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 743137f62..d3ee50c04 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -69,7 +69,7 @@ void TimelineView::mousePressEvent(QMouseEvent *event) QObject *p = this->parent(); while (p) { if (TimelineWidget *timeline = dynamic_cast(p)) { - timeline->SetTime(it.key()->time()); + timeline->SetTime(it.key()->time().in()); break; } @@ -555,8 +555,8 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { TimelineMarker *marker = *it; // Make sure marker is within In/Out points of the clip - if (marker->time_range().in() >= clip->media_in() && marker->time_range().out() <= clip->media_in() + clip->length()) { - QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time_range().in()), block_top + block_height); + if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { + QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time().in()), block_top + block_height); painter->setClipRect(r); QRect marker_rect = marker->Draw(painter, marker_pt, GetScale(), false); clip_marker_rects_.insert(marker, marker_rect); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index c56c73330..6a647bb5c 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -128,16 +128,16 @@ bool SeekableWidget::PasteMarkers() // Normalize markers to start at playhead rational min = RATIONAL_MAX; for (auto it=markers.cbegin(); it!=markers.cend(); it++) { - min = std::min(min, (*it)->time()); + min = std::min(min, (*it)->time().in()); } min -= GetTime(); for (auto it=markers.cbegin(); it!=markers.cend(); it++) { TimelineMarker *m = *it; - m->set_time(m->time() - min); + m->set_time(m->time().in() - min); - if (TimelineMarker *existing = timeline_points_->markers()->GetMarkerAtTime(m->time())) { + if (TimelineMarker *existing = timeline_points_->markers()->GetMarkerAtTime(m->time().in())) { command->add_child(new MarkerRemoveCommand(existing)); } @@ -333,12 +333,12 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) for (auto it=GetTimelinePoints()->markers()->cbegin(); it!=GetTimelinePoints()->markers()->cend(); it++) { TimelineMarker* marker = *it; - int marker_right = TimeToScene(marker->time_range().out()); + int marker_right = TimeToScene(marker->time().out()); if (marker_right < lim_left) { continue; } - int marker_left = TimeToScene(marker->time_range().in()); + int marker_left = TimeToScene(marker->time().in()); if (marker_left >= lim_right) { break; } @@ -430,16 +430,16 @@ bool SeekableWidget::FindResizeHandle(QMouseEvent *event) // Check for markers for (auto it=timeline_points_->markers()->cbegin(); it!=timeline_points_->markers()->cend(); it++) { TimelineMarker *m = *it; - if (m->time_range().in() != m->time_range().out()) { - if (m->time_range().in() >= min && m->time_range().in() < max) { + if (m->time().in() != m->time().out()) { + if (m->time().in() >= min && m->time().in() < max) { resize_mode_ = kResizeIn; - } else if (m->time_range().out() >= min && m->time_range().out() < max) { + } else if (m->time().out() >= min && m->time().out() < max) { resize_mode_ = kResizeOut; } if (resize_mode_ != kResizeNone) { resize_item_ = m; - resize_item_range_ = m->time_range(); + resize_item_range_ = m->time(); resize_snap_mask_ = TimeBasedWidget::kSnapAll; break; } @@ -508,7 +508,7 @@ void SeekableWidget::CommitResizeHandle() MultiUndoCommand *command = new MultiUndoCommand(); if (TimelineMarker *marker = dynamic_cast(resize_item_)) { - command->add_child(new MarkerChangeTimeCommand(marker, marker->time_range(), resize_item_range_)); + command->add_child(new MarkerChangeTimeCommand(marker, marker->time(), resize_item_range_)); } else if (TimelineWorkArea *workarea = dynamic_cast(resize_item_)) { command->add_child(new WorkareaSetRangeCommand(workarea, workarea->range(), resize_item_range_)); } From d6d26a9a076924b439abf4a12b8548b42f2465bb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 11:32:20 -0700 Subject: [PATCH 28/62] crashpad: fix if macro mistake --- app/common/crashpadinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/crashpadinterface.cpp b/app/common/crashpadinterface.cpp index d9b6fcb86..6cee4f9c4 100644 --- a/app/common/crashpadinterface.cpp +++ b/app/common/crashpadinterface.cpp @@ -32,7 +32,7 @@ #include "crashpadutils.h" #include "filefunctions.h" -#ifdef BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_WIN) #include #endif From 78d5314c0a76d25b7239a789353db2a5a8121d23 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 13:44:59 -0700 Subject: [PATCH 29/62] seekablewidget: don't allow deleting if dragging Fixes potential crash if a user does this --- app/widget/keyframeview/keyframeview.cpp | 12 +++++++----- app/widget/timeruler/seekablewidget.cpp | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index fba3b6299..51ec2e724 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -53,13 +53,15 @@ KeyframeView::KeyframeView(QWidget *parent) : void KeyframeView::DeleteSelected() { - MultiUndoCommand* command = new MultiUndoCommand(); + if (!selection_manager_.IsDragging()) { + MultiUndoCommand* command = new MultiUndoCommand(); - foreach (NodeKeyframe *key, GetSelectedKeyframes()) { - command->add_child(new NodeParamRemoveKeyframeCommand(key)); + foreach (NodeKeyframe *key, GetSelectedKeyframes()) { + command->add_child(new NodeParamRemoveKeyframeCommand(key)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } - - Core::instance()->undo_stack()->pushIfHasChildren(command); } KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 6a647bb5c..e915375fd 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -90,13 +90,15 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) void SeekableWidget::DeleteSelected() { - MultiUndoCommand* command = new MultiUndoCommand(); + if (!selection_manager_.IsDragging()) { + MultiUndoCommand* command = new MultiUndoCommand(); - foreach (TimelineMarker *marker, selection_manager_.GetSelectedObjects()) { - command->add_child(new MarkerRemoveCommand(marker)); + foreach (TimelineMarker *marker, selection_manager_.GetSelectedObjects()) { + command->add_child(new MarkerRemoveCommand(marker)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } - - Core::instance()->undo_stack()->pushIfHasChildren(command); } bool SeekableWidget::CopySelected(bool cut) From f17c119c29495a490fc526d457d760edf596f2be Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 13:47:12 -0700 Subject: [PATCH 30/62] timeline: handle users deleting tentative subtitle track Fixes potential crash if users try to do this --- app/widget/timelinewidget/timelinewidget.cpp | 93 +++++++++++++------ app/widget/timelinewidget/timelinewidget.h | 10 ++ .../timelinewidget/trackview/trackview.cpp | 6 +- .../timelinewidget/trackview/trackview.h | 3 + .../trackview/trackviewitem.cpp | 1 + .../timelinewidget/trackview/trackviewitem.h | 3 + 6 files changed, 85 insertions(+), 31 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 04ec0876e..47c704a76 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -66,7 +66,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) : rubberband_(QRubberBand::Rectangle, this), active_tool_(nullptr), use_audio_time_units_(false), - subtitle_show_command_(nullptr) + subtitle_show_command_(nullptr), + subtitle_tentative_track_(nullptr) { QVBoxLayout* vert_layout = new QVBoxLayout(this); vert_layout->setSpacing(0); @@ -91,13 +92,13 @@ TimelineWidget::TimelineWidget(QWidget *parent) : vert_layout->addWidget(view_splitter_); // Video view - views_.append(new TimelineAndTrackView(Qt::AlignBottom)); + views_.append(AddTimelineAndTrackView(Qt::AlignBottom)); // Audio view - views_.append(new TimelineAndTrackView(Qt::AlignTop)); + views_.append(AddTimelineAndTrackView(Qt::AlignTop)); // Subtitle view - views_.append(new TimelineAndTrackView(Qt::AlignTop)); + views_.append(AddTimelineAndTrackView(Qt::AlignTop)); // Create tools tools_.resize(olive::Tool::kCount); @@ -780,6 +781,44 @@ void TimelineWidget::DisableRecordingOverlay() } } +void TimelineWidget::AddTentativeSubtitleTrack() +{ + if (!subtitle_show_command_) { + // Determine if we need to do anything + QList sz = view_splitter_->sizes(); + bool should_adjust_splitter = (sz[Track::kSubtitle] == 0); + bool should_add_sub_track = (sequence() && sequence()->track_list(Track::kSubtitle)->GetTrackCount() == 0); + + if (should_adjust_splitter || should_add_sub_track) { + // Create command + subtitle_show_command_ = new MultiUndoCommand(); + + if (should_adjust_splitter) { + sz[Track::kSubtitle] = height() / Track::kCount; + subtitle_show_command_->add_child(new SetSplitterSizesCommand(view_splitter_, sz)); + } + + if (should_add_sub_track) { + TimelineAddTrackCommand *track_add_cmd = new TimelineAddTrackCommand(sequence()->track_list(Track::kSubtitle)); + subtitle_tentative_track_ = track_add_cmd->track(); + subtitle_show_command_->add_child(track_add_cmd); + } + + subtitle_show_command_->redo_now(); + } + } +} + +void TimelineWidget::ClearTentativeSubtitleTrack() +{ + if (subtitle_show_command_) { + subtitle_show_command_->undo_now(); + delete subtitle_show_command_; + subtitle_show_command_ = nullptr; + subtitle_tentative_track_ = nullptr; + } +} + void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command) { for (int i=0;itool() == Tool::kAdd && Core::instance()->GetSelectedAddableObject() == Tool::kAddableSubtitle) { - if (!subtitle_show_command_) { - // Determine if we need to do anything - QList sz = view_splitter_->sizes(); - bool should_adjust_splitter = (sz[Track::kSubtitle] == 0); - bool should_add_sub_track = (sequence() && sequence()->track_list(Track::kSubtitle)->GetTrackCount() == 0); - - if (should_adjust_splitter || should_add_sub_track) { - // Create command - subtitle_show_command_ = new MultiUndoCommand(); - - if (should_adjust_splitter) { - sz[Track::kSubtitle] = height() / Track::kCount; - subtitle_show_command_->add_child(new SetSplitterSizesCommand(view_splitter_, sz)); - } - - if (should_add_sub_track) { - subtitle_show_command_->add_child(new TimelineAddTrackCommand(sequence()->track_list(Track::kSubtitle))); - } - - subtitle_show_command_->redo_now(); - } - } - } else if (subtitle_show_command_) { - subtitle_show_command_->undo_now(); - delete subtitle_show_command_; - subtitle_show_command_ = nullptr; + AddTentativeSubtitleTrack(); + } else { + ClearTentativeSubtitleTrack(); } } @@ -1210,6 +1226,16 @@ void TimelineWidget::RenameSelectedBlocks() Core::instance()->undo_stack()->pushIfHasChildren(command); } +void TimelineWidget::TrackAboutToBeDeleted(Track *track) +{ + if (track == subtitle_tentative_track_) { + // User is deleting the tentative subtitle track. Technically they shouldn't do this, but they + // might if they misinterpret it as permanent. If so, we handle it cleanly by pushing our + // command as if the action really were permanent. + Core::instance()->undo_stack()->push(TakeSubtitleSectionCommand()); + } +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); @@ -1641,6 +1667,13 @@ bool TimelineWidget::PasteInternal(bool insert) return true; } +TimelineAndTrackView *TimelineWidget::AddTimelineAndTrackView(Qt::Alignment alignment) +{ + TimelineAndTrackView *v = new TimelineAndTrackView(alignment); + connect(v->track_view(), &TrackView::AboutToDeleteTrack, this, &TimelineWidget::TrackAboutToBeDeleted); + return v; +} + QByteArray TimelineWidget::SaveSplitterState() const { return view_splitter_->saveState(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 99d3e7f1f..84d10e7fd 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -107,6 +107,10 @@ public: void DisableRecordingOverlay(); + void AddTentativeSubtitleTrack(); + + void ClearTentativeSubtitleTrack(); + /** * @brief Timelines should always be connected to sequences */ @@ -230,6 +234,7 @@ public: // Set to null subtitle_show_command_ = nullptr; + subtitle_tentative_track_ = nullptr; // Return command return c; @@ -298,6 +303,8 @@ private: bool PasteInternal(bool insert); + TimelineAndTrackView *AddTimelineAndTrackView(Qt::Alignment alignment); + QPoint drag_origin_; QRubberBand rubberband_; @@ -331,6 +338,7 @@ private: QSplitter* view_splitter_; MultiUndoCommand *subtitle_show_command_; + Track *subtitle_tentative_track_; QTimer *signal_block_change_timer_; @@ -420,6 +428,8 @@ private slots: void RenameSelectedBlocks(); + void TrackAboutToBeDeleted(Track *track); + }; } diff --git a/app/widget/timelinewidget/trackview/trackview.cpp b/app/widget/timelinewidget/trackview/trackview.cpp index fde1e8804..562c68d55 100644 --- a/app/widget/timelinewidget/trackview/trackview.cpp +++ b/app/widget/timelinewidget/trackview/trackview.cpp @@ -121,9 +121,13 @@ void TrackView::TrackHeightChanged(int index, int height) void TrackView::InsertTrack(Track *track) { + TrackViewItem *tvi = new TrackViewItem(track); + + connect(tvi, &TrackViewItem::AboutToDeleteTrack, this, &TrackView::AboutToDeleteTrack); + splitter_->Insert(track->Index(), track->GetTrackHeightInPixels(), - new TrackViewItem(track)); + tvi); } void TrackView::RemoveTrack(Track *track) diff --git a/app/widget/timelinewidget/trackview/trackview.h b/app/widget/timelinewidget/trackview/trackview.h index 89f131333..1498a526b 100644 --- a/app/widget/timelinewidget/trackview/trackview.h +++ b/app/widget/timelinewidget/trackview/trackview.h @@ -40,6 +40,9 @@ public: void ConnectTrackList(TrackList* list); void DisconnectTrackList(); +signals: + void AboutToDeleteTrack(Track *track); + protected: virtual void resizeEvent(QResizeEvent *e) override; diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index da84f8b27..6a5ba698e 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -145,6 +145,7 @@ void TrackViewItem::ShowContextMenu(const QPoint &p) void TrackViewItem::DeleteTrack() { + emit AboutToDeleteTrack(track_); Core::instance()->undo_stack()->push(new TimelineRemoveTrackCommand(track_)); } diff --git a/app/widget/timelinewidget/trackview/trackviewitem.h b/app/widget/timelinewidget/trackview/trackviewitem.h index d497c0241..7ec2eb750 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.h +++ b/app/widget/timelinewidget/trackview/trackviewitem.h @@ -38,6 +38,9 @@ public: TrackViewItem(Track* track, QWidget* parent = nullptr); +signals: + void AboutToDeleteTrack(Track *track); + private: QPushButton* CreateMSLButton(const QColor &checked_color) const; From 83e7707025ac63f5285d71b0980e0349c8b44cdd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 13:48:59 -0700 Subject: [PATCH 31/62] media: implement importing SRT files --- app/codec/ffmpeg/ffmpegdecoder.cpp | 73 +++++++- app/codec/ffmpeg/ffmpegdecoder.h | 4 + app/common/timecodefunctions.cpp | 17 +- .../footageproperties/footageproperties.cpp | 27 ++- app/node/block/subtitle/subtitle.cpp | 6 +- app/node/output/viewer/viewer.cpp | 39 +++++ app/node/output/viewer/viewer.h | 28 ++- app/node/project/footage/footage.cpp | 49 +++++- app/node/project/footage/footage.h | 1 + .../project/footage/footagedescription.cpp | 10 ++ app/node/project/footage/footagedescription.h | 34 +++- app/node/value.cpp | 6 + app/node/value.h | 7 + app/render/opengl/openglrenderer.cpp | 1 + app/render/subtitleparams.cpp | 54 ++++++ app/render/subtitleparams.h | 70 +++++++- .../nodeparamviewwidgetbridge.cpp | 3 + app/widget/timelinewidget/tool/import.cpp | 162 +++++++++++------- app/widget/timelinewidget/tool/import.h | 2 + 19 files changed, 517 insertions(+), 76 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 165807418..caf9d6857 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -46,6 +46,7 @@ extern "C" { #include "common/timecodefunctions.h" #include "render/framehashcache.h" #include "render/diskmanager.h" +#include "render/subtitleparams.h" namespace olive { @@ -415,7 +416,51 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - qDebug() << "Subtitle probing: Stub"; + // Limit to SRT for now... + if (avstream->codecpar->codec_id == AV_CODEC_ID_SUBRIP) { + SubtitleParams sub; + + AVPacket* pkt = av_packet_alloc(); + { + Instance instance; + instance.Open(filename_c, avstream->index); + + //qDebug() << instance.GetSubtitleHeader(); + + AVSubtitle avsub; + while (instance.GetSubtitle(pkt, &avsub) >= 0) { + for (unsigned int j=0; jass; + + int comma = 0; + for (int k=0; kpts, avstream->time_base), + Timecode::timestamp_to_time(pkt->pts + pkt->duration, avstream->time_base)); + + sub.push_back(Subtitle(time, ass)); + } + avsubtitle_free(&avsub); + } + + instance.Close(); + } + av_packet_free(&pkt); + + desc.AddSubtitleStream(sub); + } } @@ -1120,6 +1165,32 @@ int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) return ret; } +const char *FFmpegDecoder::Instance::GetSubtitleHeader() const +{ + return reinterpret_cast(codec_ctx_->subtitle_header); +} + +int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) +{ + int ret; + + do { + av_packet_unref(pkt); + + ret = av_read_frame(fmt_ctx_, pkt); + } while (pkt->stream_index != avstream_->index && ret >= 0); + + if (ret >= 0) { + int got_sub; + ret = avcodec_decode_subtitle2(codec_ctx_, sub, &got_sub, pkt); + if (!got_sub) { + ret = -1; + } + } + + return ret; +} + void FFmpegDecoder::Instance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 612aa8533..13535cf0f 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -91,6 +91,10 @@ private: */ int GetFrame(AVPacket* pkt, AVFrame* frame); + const char *GetSubtitleHeader() const; + + int GetSubtitle(AVPacket* pkt, AVSubtitle* sub); + void Seek(int64_t timestamp); AVFormatContext* fmt_ctx() const diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 8402666f7..a0f97efb1 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -20,6 +20,10 @@ #include "timecodefunctions.h" +extern "C" { +#include +} + #include #include "config/config.h" @@ -254,7 +258,14 @@ rational Timecode::snap_time_to_timebase(const rational &time, const rational &t rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &timebase) { - return rational(timestamp) * timebase; + int64_t num = int64_t(timebase.numerator()) * timestamp; + int64_t den = timebase.denominator(); + + int num_r, den_r; + + av_reduce(&num_r, &den_r, num, den, INT_MAX); + + return rational(num_r, den_r); } QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) @@ -310,7 +321,7 @@ int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source, c return ts; } - return qRound64(static_cast(ts) * source.toDouble() / dest.toDouble()); + return av_rescale_q(ts, source.toAVRational(), dest.toAVRational()); } int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &source, const rational &dest) @@ -319,7 +330,7 @@ int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &sour return ts; } - return qCeil(static_cast(ts) * source.toDouble() / dest.toDouble()); + return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(), AV_ROUND_UP); } } diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp index 7cb56c63a..d33517766 100644 --- a/app/dialog/footageproperties/footageproperties.cpp +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -93,6 +93,15 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count())); break; } + case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(reference.index()); + is_enabled = sp.enabled(); + + // FIXME: Language? + description = tr("Subtitles"); + break; + } default: stacked_widget_->addWidget(new StreamProperties()); description = tr("Unknown"); @@ -106,7 +115,8 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota if (first_usable_stream == -1 && (reference.type() == Track::kVideo - || reference.type() == Track::kAudio)) { + || reference.type() == Track::kAudio + || reference.type() == Track::kSubtitle)) { first_usable_stream = i; } } @@ -163,6 +173,8 @@ void FootagePropertiesDialog::accept() old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled(); break; case Track::kSubtitle: + old_stream_enabled = footage_->GetSubtitleParams(reference.index()).enabled(); + break; case Track::kNone: case Track::kCount: break; @@ -218,6 +230,13 @@ void FootagePropertiesDialog::StreamEnableChangeCommand::redo() break; } case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(index_); + old_enabled_ = sp.enabled(); + sp.set_enabled(new_enabled_); + footage_->SetSubtitleParams(sp, index_); + break; + } case Track::kNone: case Track::kCount: break; @@ -242,6 +261,12 @@ void FootagePropertiesDialog::StreamEnableChangeCommand::undo() break; } case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(index_); + sp.set_enabled(old_enabled_); + footage_->SetSubtitleParams(sp, index_); + break; + } case Track::kNone: case Track::kCount: break; diff --git a/app/node/block/subtitle/subtitle.cpp b/app/node/block/subtitle/subtitle.cpp index d0930adbb..25bccb6f4 100644 --- a/app/node/block/subtitle/subtitle.cpp +++ b/app/node/block/subtitle/subtitle.cpp @@ -43,7 +43,11 @@ SubtitleBlock::SubtitleBlock() QString SubtitleBlock::Name() const { - return tr("Subtitle"); + if (GetText().isEmpty()) { + return tr("Subtitle"); + } else { + return GetText(); + } } QString SubtitleBlock::id() const diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index ff1ef27a9..536d6de39 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -28,6 +28,7 @@ namespace olive { const QString ViewerOutput::kVideoParamsInput = QStringLiteral("video_param_in"); const QString ViewerOutput::kAudioParamsInput = QStringLiteral("audio_param_in"); +const QString ViewerOutput::kSubtitleParamsInput = QStringLiteral("subtitle_param_in"); const QString ViewerOutput::kTextureInput = QStringLiteral("tex_in"); const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); const QString ViewerOutput::kVideoAutoCacheInput = QStringLiteral("video_autocache_in"); @@ -48,6 +49,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + AddInput(kSubtitleParamsInput, NodeValue::kSubtitleParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + if (create_buffer_inputs) { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); @@ -100,6 +103,7 @@ QString ViewerOutput::duration() const // Get first enabled streams VideoParams video = GetFirstEnabledVideoStream(); AudioParams audio = GetFirstEnabledAudioStream(); + SubtitleParams sub = GetFirstEnabledSubtitleStream(); if (video.is_valid() && video.video_type() != VideoParams::kVideoTypeStill) { // Prioritize video @@ -113,6 +117,8 @@ QString ViewerOutput::duration() const } using_timebase = audio.sample_rate_as_time_base(); + } else if (sub.is_valid()) { + using_timebase = OLIVE_CONFIG("DefaultSequenceFrameRate").value(); } if (using_timebase.isNull()) { @@ -151,6 +157,11 @@ bool ViewerOutput::HasEnabledAudioStreams() const return GetFirstEnabledAudioStream().is_valid(); } +bool ViewerOutput::HasEnabledSubtitleStreams() const +{ + return GetFirstEnabledSubtitleStream().is_valid(); +} + VideoParams ViewerOutput::GetFirstEnabledVideoStream() const { int sz = GetVideoStreamCount(); @@ -181,6 +192,21 @@ AudioParams ViewerOutput::GetFirstEnabledAudioStream() const return AudioParams(); } +SubtitleParams ViewerOutput::GetFirstEnabledSubtitleStream() const +{ + int sz = GetSubtitleStreamCount(); + + for (int i=0; i ViewerOutput::GetEnabledStreamsAsReferences() const } } + { + int sp_sz = GetSubtitleStreamCount(); + + for (int i=0; i(); + } else { + return SubtitleParams(); + } + } + void SetVideoParams(const VideoParams &video, int index = 0) { SetStandardValue(kVideoParamsInput, QVariant::fromValue(video), index); @@ -98,6 +109,11 @@ public: SetStandardValue(kAudioParamsInput, QVariant::fromValue(audio), index); } + void SetSubtitleParams(const SubtitleParams &subs, int index = 0) + { + SetStandardValue(kSubtitleParamsInput, QVariant::fromValue(subs), index); + } + int GetVideoStreamCount() const { return InputArraySize(kVideoParamsInput); @@ -108,16 +124,23 @@ public: return InputArraySize(kAudioParamsInput); } + int GetSubtitleStreamCount() const + { + return InputArraySize(kSubtitleParamsInput); + } + int GetTotalStreamCount() const { - return GetVideoStreamCount() + GetAudioStreamCount(); + return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount(); } bool HasEnabledVideoStreams() const; bool HasEnabledAudioStreams() const; + bool HasEnabledSubtitleStreams() const; VideoParams GetFirstEnabledVideoStream() const; AudioParams GetFirstEnabledAudioStream() const; + SubtitleParams GetFirstEnabledSubtitleStream() const; const rational &GetLength() const { return last_length_; } const rational &GetVideoLength() const { return video_length_; } @@ -191,6 +214,7 @@ public: static const QString kVideoParamsInput; static const QString kAudioParamsInput; + static const QString kSubtitleParamsInput; static const QString kTextureInput; static const QString kSamplesInput; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 55f4043e4..5ce6afab3 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -121,6 +121,10 @@ void Footage::InputValueChangedEvent(const QString &input, int element) AddStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i))); } + for (int i=0; i& GetSubtitleStreams() const + { + return subtitle_streams_; + } + private: - static constexpr unsigned kFootageMetaVersion = 1; + static constexpr unsigned kFootageMetaVersion = 2; QString decoder_; @@ -120,6 +146,8 @@ private: QVector audio_streams_; + QVector subtitle_streams_; + }; } diff --git a/app/node/value.cpp b/app/node/value.cpp index 3b51a4a74..984745102 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -29,6 +29,7 @@ #include "common/bezier.h" #include "common/tohex.h" #include "render/audioparams.h" +#include "render/subtitleparams.h" #include "render/videoparams.h" #include "render/color.h" @@ -130,6 +131,7 @@ QByteArray NodeValue::ValueToBytes(NodeValue::Type type, const QVariant &value) return value.value().toBytes(); // These types have no persistent input + case kSubtitleParams: case kNone: case kTexture: case kSamples: @@ -345,6 +347,8 @@ QString NodeValue::GetPrettyDataTypeName(Type type) return QCoreApplication::translate("NodeValue", "Video Parameters"); case kAudioParams: return QCoreApplication::translate("NodeValue", "Audio Parameters"); + case kSubtitleParams: + return QCoreApplication::translate("NodeValue", "Subtitle Parameters"); case kDataTypeCount: break; @@ -394,6 +398,8 @@ QString NodeValue::GetDataTypeName(Type type) return QStringLiteral("vparam"); case kAudioParams: return QStringLiteral("aparam"); + case kSubtitleParams: + return QStringLiteral("sparam"); case kDataTypeCount: break; } diff --git a/app/node/value.h b/app/node/value.h index 67be1425d..3a2309c4f 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -178,6 +178,13 @@ public: */ kAudioParams, + /** + * Subtitle Parameters type + * + * Resolves to `SubtitleParams` + */ + kSubtitleParams, + /** * End of list */ diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index cfdf00eb1..99e80c104 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -518,6 +518,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video case NodeValue::kFile: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kBezier: case NodeValue::kNone: case NodeValue::kDataTypeCount: diff --git a/app/render/subtitleparams.cpp b/app/render/subtitleparams.cpp index 67118bdd3..442fa3994 100644 --- a/app/render/subtitleparams.cpp +++ b/app/render/subtitleparams.cpp @@ -22,6 +22,8 @@ #include +#include "common/xmlutils.h" + namespace olive { QString SubtitleParams::GenerateASSHeader() @@ -106,4 +108,56 @@ QString SubtitleParams::GenerateASSHeader() return ass_code; } +void SubtitleParams::Load(QXmlStreamReader *reader) +{ + this->clear(); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("streamindex")) { + set_stream_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("enabled")) { + set_enabled(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("subtitles")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("subtitle")) { + rational in, out; + QString text; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + text = reader->readElementText(); + + this->push_back(Subtitle(TimeRange(in, out), text)); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void SubtitleParams::Save(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("streamindex"), QString::number(stream_index_)); + writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_)); + + writer->writeStartElement(QStringLiteral("subtitles")); + for (auto it=this->cbegin(); it!=this->cend(); it++) { + writer->writeStartElement(QStringLiteral("subtitle")); + writer->writeAttribute(QStringLiteral("in"), it->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), it->time().out().toString()); + writer->writeCharacters(it->text()); + writer->writeEndElement(); // subtitle + } + writer->writeEndElement(); // subtitles +} + } diff --git a/app/render/subtitleparams.h b/app/render/subtitleparams.h index 065247e41..2e9d86c8e 100644 --- a/app/render/subtitleparams.h +++ b/app/render/subtitleparams.h @@ -21,16 +21,84 @@ #ifndef SUBTITLEPARAMS_H #define SUBTITLEPARAMS_H +#include #include +#include +#include + +#include "common/timerange.h" namespace olive { -class SubtitleParams { +class Subtitle +{ public: + Subtitle() = default; + + Subtitle(const TimeRange &time, const QString &text) : + range_(time), + text_(text) + { + } + + const TimeRange &time() const { return range_; } + void set_time(const TimeRange &t) { range_ = t; } + + const QString &text() const { return text_; } + void set_text(const QString &t) { text_ = t; } + +private: + TimeRange range_; + + QString text_; + +}; + +class SubtitleParams : public std::vector +{ +public: + SubtitleParams() + { + stream_index_ = 0; + enabled_ = true; + } + static QString GenerateASSHeader(); + void Load(QXmlStreamReader* reader); + + void Save(QXmlStreamWriter* writer) const; + + bool is_valid() const + { + return !this->empty(); + } + + rational duration() const + { + if (this->empty()) { + return 0; + } else { + return back().time().out(); + } + } + + int stream_index() const { return stream_index_; } + void set_stream_index(int i) { stream_index_ = i; } + + bool enabled() const { return enabled_; } + void set_enabled(bool e) { enabled_ = e; } + +private: + int stream_index_; + + bool enabled_; + }; } +Q_DECLARE_METATYPE(olive::Subtitle) +Q_DECLARE_METATYPE(olive::SubtitleParams) + #endif // SUBTITLEPARAMS_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index aff1fb770..9f0ae6ac8 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -89,6 +89,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -240,6 +241,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -417,6 +419,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index a5cda4a32..d17b9a3d8 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -30,6 +30,7 @@ #include "core.h" #include "dialog/sequence/sequence.h" #include "node/audio/volume/volume.h" +#include "node/block/subtitle/subtitle.h" #include "node/distort/transform/transformdistortnode.h" #include "node/generator/matrix/matrix.h" #include "node/math/math/math.h" @@ -154,6 +155,7 @@ void ImportTool::DragLeave(QDragLeaveEvent* event) { if (!dragged_footage_.isEmpty()) { parent()->ClearGhosts(); + parent()->ClearTentativeSubtitleTrack(); dragged_footage_.clear(); event->accept(); @@ -235,25 +237,27 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData // Create ghosts foreach (const Track::Reference& ref, it->second) { Track::Type track_type = ref.type(); + Track::Reference dest_track(track_type, track_offsets.at(track_type)); - TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); + if (track_type == Track::kVideo || track_type == Track::kAudio) { + auto ghost = CreateGhost(TimeRange(ghost_start, ghost_start + footage_duration), ghost_in, dest_track); - ghost->SetIn(ghost_start); - ghost->SetOut(ghost_start + footage_duration); - ghost->SetMediaIn(ghost_in); - ghost->SetTrack(Track::Reference(track_type, track_offsets.at(track_type))); + // Increment track count for this track type + track_offsets[track_type]++; - snap_points_.push_back(ghost->GetIn()); - snap_points_.push_back(ghost->GetOut()); + TimelineViewGhostItem::AttachedFootage af = {it->first, ref.ToString()}; + ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(af)); + } else if (track_type == Track::kSubtitle) { + SubtitleParams sp = footage->GetSubtitleParams(ref.index()); - // Increment track count for this track type - track_offsets[track_type]++; + for (const Subtitle &sub : sp) { + auto ghost = CreateGhost(sub.time() + ghost_start, 0, dest_track); - TimelineViewGhostItem::AttachedFootage af = {it->first, ref.ToString()}; - ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(af)); - ghost->SetMode(Timeline::kMove); + ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(sub)); + } - parent()->AddGhost(ghost); + parent()->AddTentativeSubtitleTrack(); + } } // Stack each ghost one after the other @@ -276,6 +280,10 @@ void ImportTool::DropGhosts(bool insert) { MultiUndoCommand* command = new MultiUndoCommand(); + if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) { + command->add_child(c); + } + NodeGraph* dst_graph = nullptr; Sequence* sequence = this->sequence(); bool open_sequence = false; @@ -384,69 +392,83 @@ void ImportTool::DropGhosts(bool insert) for (int i=0;iGetGhostItems().size();i++) { TimelineViewGhostItem* ghost = parent()->GetGhostItems().at(i); + Block* block = nullptr; - TimelineViewGhostItem::AttachedFootage footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + Track::Type track_type = ghost->GetAdjustedTrack().type(); + if (track_type == Track::kVideo || track_type == Track::kAudio) { + TimelineViewGhostItem::AttachedFootage footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); - ClipBlock* clip = new ClipBlock(); - clip->set_media_in(ghost->GetMediaIn()); - clip->set_length_and_media_out(ghost->GetLength()); - clip->SetLabel(footage_stream.footage->GetLabel()); - command->add_child(new NodeAddCommand(dst_graph, clip)); + ClipBlock* clip = new ClipBlock(); + block = clip; + clip->set_media_in(ghost->GetMediaIn()); + clip->SetLabel(footage_stream.footage->GetLabel()); + command->add_child(new NodeAddCommand(dst_graph, clip)); - // Position clip in its own context - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); + // Position clip in its own context + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); - int dep_pos = kDefaultDistanceFromOutput; + int dep_pos = kDefaultDistanceFromOutput; - // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); + // Position footage in its context + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); - dep_pos++; + dep_pos++; - switch (Track::Reference::TypeFromString(footage_stream.output)) { - case Track::kVideo: - { - TransformDistortNode* transform = new TransformDistortNode(); - command->add_child(new NodeAddCommand(dst_graph, transform)); + switch (Track::Reference::TypeFromString(footage_stream.output)) { + case Track::kVideo: + { + TransformDistortNode* transform = new TransformDistortNode(); + command->add_child(new NodeAddCommand(dst_graph, transform)); - command->add_child(new NodeSetValueHintCommand(transform, TransformDistortNode::kTextureInput, -1, Node::ValueHint({NodeValue::kTexture}, footage_stream.output))); + command->add_child(new NodeSetValueHintCommand(transform, TransformDistortNode::kTextureInput, -1, Node::ValueHint({NodeValue::kTexture}, footage_stream.output))); - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); - command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); - break; + command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); + command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); + break; + } + case Track::kAudio: + { + VolumeNode* volume_node = new VolumeNode(); + command->add_child(new NodeAddCommand(dst_graph, volume_node)); + + command->add_child(new NodeSetValueHintCommand(volume_node, VolumeNode::kSamplesInput, -1, Node::ValueHint({NodeValue::kSamples}, footage_stream.output))); + + command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); + command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); + break; + } + default: + break; + } + + // Link any clips so far that share the same Footage with this one + for (int j=0;jGetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + + if (footage_compare.footage == footage_stream.footage) { + Block::Link(block_items.at(j), clip); + } + } + } else if (track_type == Track::kSubtitle) { + Subtitle src = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + SubtitleBlock *sub = new SubtitleBlock(); + sub->SetText(src.text()); + block = sub; + + command->add_child(new NodeAddCommand(dst_graph, sub)); + command->add_child(new NodeSetPositionCommand(sub, sub, QPointF(0, 0))); } - case Track::kAudio: - { - VolumeNode* volume_node = new VolumeNode(); - command->add_child(new NodeAddCommand(dst_graph, volume_node)); - command->add_child(new NodeSetValueHintCommand(volume_node, VolumeNode::kSamplesInput, -1, Node::ValueHint({NodeValue::kSamples}, footage_stream.output))); - - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); - command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); - break; - } - default: - break; - } + block->set_length_and_media_out(ghost->GetLength()); command->add_child(new TrackPlaceBlockCommand(sequence->track_list(ghost->GetAdjustedTrack().type()), ghost->GetAdjustedTrack().index(), - clip, + block, ghost->GetAdjustedIn())); - block_items.replace(i, clip); - - // Link any clips so far that share the same Footage with this one - for (int j=0;jGetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value(); - - if (footage_compare.footage == footage_stream.footage) { - Block::Link(block_items.at(j), clip); - } - } + block_items.replace(i, block); } } @@ -460,4 +482,24 @@ void ImportTool::DropGhosts(bool insert) dragged_footage_.clear(); } +TimelineViewGhostItem* ImportTool::CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track) +{ + TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); + + ghost->SetIn(range.in()); + ghost->SetOut(range.out()); + ghost->SetMediaIn(media_in); + ghost->SetTrack(track); + + snap_points_.push_back(ghost->GetIn()); + snap_points_.push_back(ghost->GetOut()); + + + ghost->SetMode(Timeline::kMove); + + parent()->AddGhost(ghost); + + return ghost; +} + } diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index d2e59a578..2640f2708 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -54,6 +54,8 @@ private: void DropGhosts(bool insert); + TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track); + DraggedFootageData dragged_footage_; int import_pre_buffer_; From c0ad24439782637662e546209ddacdc89a1349fd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 14:55:07 -0700 Subject: [PATCH 32/62] nodeview: implement dragging items from project explorer into node view --- app/node/project/project.cpp | 2 + app/node/project/project.h | 2 + app/node/project/projectviewmodel.cpp | 8 +- app/widget/nodeview/nodeview.cpp | 396 ++++++++++++++-------- app/widget/nodeview/nodeview.h | 8 + app/widget/timelinewidget/tool/import.cpp | 4 +- app/widget/viewer/footageviewer.cpp | 2 +- app/widget/viewer/viewer.cpp | 4 +- 8 files changed, 281 insertions(+), 145 deletions(-) diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 8a28ed056..4cf192643 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -32,6 +32,8 @@ namespace olive { +const QString Project::kItemMimeType = QStringLiteral("application/x-oliveprojectitemdata"); + Project::Project() : is_modified_(false), autorecovery_saved_(true) diff --git a/app/node/project/project.h b/app/node/project/project.h index 558a051aa..09e7e8606 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -117,6 +117,8 @@ public: */ static Project *GetProjectFromObject(const QObject *o); + static const QString kItemMimeType; + signals: void NameChanged(); diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index 72c1219a5..3aefbb13d 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -271,7 +271,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const QStringList ProjectViewModel::mimeTypes() const { // Allow data from this model and a file list from external sources - return {QStringLiteral("application/x-oliveprojectitemdata"), QStringLiteral("text/uri-list")}; + return {Project::kItemMimeType, QStringLiteral("text/uri-list")}; } QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const @@ -312,7 +312,7 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const } // Set byte array as the mime data and return the mime data - data->setData(QStringLiteral("application/x-oliveprojectitemdata"), encoded_data); + data->setData(Project::kItemMimeType, encoded_data); return data; } @@ -331,9 +331,9 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action // Probe mime data for its format QStringList mime_formats = data->formats(); - if (mime_formats.contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (mime_formats.contains(Project::kItemMimeType)) { // Data is drag/drop data from this model - QByteArray model_data = data->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray model_data = data->data(Project::kItemMimeType); // Use QDataStream to deserialize the data QDataStream stream(&model_data, QIODevice::ReadOnly); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index f40a654f1..dd1aa256c 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -539,78 +540,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) // See if there are any items attached if (!attached_items_.isEmpty()) { - // Move those items to the cursor - MoveAttachedNodesToCursor(event->pos()); - - // See if the user clicked on an edge (only when dropping single nodes) - if (attached_items_.size() == 1) { - Node* attached_node = attached_items_.first().item->GetNode(); - - QRect edge_detect_rect(event->pos(), event->pos()); - - int edge_detect_radius = fontMetrics().height(); - edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius); - - QList items = this->items(edge_detect_rect); - - NodeViewEdge* new_drop_edge = nullptr; - - // See if there is an edge here - for (QGraphicsItem* item : qAsConst(items)) { - new_drop_edge = dynamic_cast(item); - - if (new_drop_edge) { - drop_input_.Reset(); - - NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); - - // Determine best input to connect to our new node - if (attached_node->GetEffectInput().IsValid()) { - // If node specifies an effect input, use that immediately - drop_input_ = attached_node->GetEffectInput(); - } else { - // Otherwise, we may have to iterate to find a valid one - for (const QString& input : attached_node->inputs()) { - if (input == Node::kEnabledInput) { - // Ignore enabled input - continue; - } - - NodeInput i(attached_node, input); - - if (attached_node->IsInputConnectable(input)) { - if (attached_node->GetInputDataType(input) == drop_edge_data_type) { - // Found exactly the type we're looking for, set and break this loop - drop_input_ = i; - break; - } else if (!drop_input_.IsValid()) { - // Default to first connectable input - drop_input_ = i; - } - } - } - } - - if (drop_input_.IsValid()) { - break; - } else { - new_drop_edge = nullptr; - } - } - } - - if (drop_edge_ != new_drop_edge) { - if (drop_edge_) { - drop_edge_->SetHighlighted(false); - } - - drop_edge_ = new_drop_edge; - - if (drop_edge_) { - drop_edge_->SetHighlighted(true); - } - } - } + ProcessMovingAttachedNodes(event->pos()); } } @@ -630,72 +560,10 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) bool had_attached_items = !attached_items_.isEmpty(); if (!attached_items_.isEmpty()) { - select_context = nullptr; - - QList items_at_cursor = this->items(event->pos()); - foreach (QGraphicsItem *i, items_at_cursor) { - if (NodeViewContext *context_item = dynamic_cast(i)) { - select_context = context_item->GetContext(); - break; - } - } + select_context = GetContextAtMousePos(event->pos()); if (select_context) { - { - MultiUndoCommand *add_command = new MultiUndoCommand(); - - foreach (const AttachedItem &ai, attached_items_) { - // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); - - // Add node to the context - if (ai.item) { - add_command->add_child(new NodeSetPositionCommand(ai.node, select_context, scene_.context_map().value(select_context)->MapScenePosToNodePosInContext(ai.item->pos()))); - select_nodes.append(ai.node); - } - } - - if (add_command->child_count()) { - add_command->redo_now(); - command->add_child(add_command); - } else { - delete add_command; - } - } - - { - // Dropped attached item onto an edge, connect it between them - MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached_items_.size() == 1) { - Node* dropping_node = nullptr; - - foreach (const AttachedItem &ai, attached_items_) { - if (ai.item) { - dropping_node = ai.node; - break; - } - } - - if (dropping_node && drop_edge_) { - // Remove old edge - drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); - - // Place new edges - drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); - } - - drop_edge_ = nullptr; - } - if (drop_edge_command->child_count()) { - drop_edge_command->redo_now(); - command->add_child(drop_edge_command); - } else { - delete drop_edge_command; - } - } - - DetachItemsFromCursor(false); + select_nodes = ProcessDroppingAttachedNodes(command, select_context, event->pos()); } else { QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); } @@ -733,6 +601,96 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event) } } +void NodeView::dragEnterEvent(QDragEnterEvent *event) +{ + if (contexts_.empty()) { + event->ignore(); + return; + } + + QStringList mime_fmts = event->mimeData()->formats(); + + if (mime_fmts.contains(Project::kItemMimeType)) { + QByteArray model_data = event->mimeData()->data(Project::kItemMimeType); + QDataStream stream(&model_data, QIODevice::ReadOnly); + + // Variables to deserialize into + quintptr item_ptr; + QVector enabled_streams; + QVector new_attached; + + int y = 0; + + while (!stream.atEnd()) { + stream >> enabled_streams >> item_ptr; + + // Get Item object + Node* item = reinterpret_cast(item_ptr); + + if (ViewerOutput* f = dynamic_cast(item)) { + NodeViewItem *new_item; + + new_item = new NodeViewItem(f, nullptr); + new_item->SetFlowDirection(scene_.GetFlowDirection()); + y++; + scene_.addItem(new_item); + + new_attached.append({new_item, f, QPointF(0, y)}); + } + } + + if (new_attached.empty()) { + event->ignore(); + } else { + SetAttachedItems(new_attached); + + event->accept(); + } + } +} + +void NodeView::dragMoveEvent(QDragMoveEvent *event) +{ + if (attached_items_.empty()) { + event->ignore(); + } else { + ProcessMovingAttachedNodes(event->pos()); + + if (GetContextAtMousePos(event->pos())) { + event->accept(); + } else { + event->ignore(); + } + } +} + +void NodeView::dropEvent(QDropEvent *event) +{ + if (Node *drop_ctx = GetContextAtMousePos(event->pos())) { + MultiUndoCommand *command = new MultiUndoCommand(); + QVector select_nodes = ProcessDroppingAttachedNodes(command, drop_ctx, event->pos()); + Core::instance()->undo_stack()->pushIfHasChildren(command); + + scene_.context_map().value(drop_ctx)->Select(select_nodes); + + event->accept(); + } else { + DetachItemsFromCursor(false); + event->ignore(); + } +} + +void NodeView::dragLeaveEvent(QDragLeaveEvent *event) +{ + if (attached_items_.empty()) { + event->ignore(); + } else { + DetachItemsFromCursor(false); + + event->accept(); + } +} + void NodeView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -1030,6 +988,172 @@ void NodeView::MoveAttachedNodesToCursor(const QPoint& p) } } +void NodeView::ProcessMovingAttachedNodes(const QPoint &pos) +{ + // Move those items to the cursor + MoveAttachedNodesToCursor(pos); + + // See if the user clicked on an edge (only when dropping single nodes) + if (attached_items_.size() == 1) { + Node* attached_node = attached_items_.first().item->GetNode(); + + QRect edge_detect_rect(pos, pos); + + int edge_detect_radius = fontMetrics().height(); + edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius); + + QList items = this->items(edge_detect_rect); + + NodeViewEdge* new_drop_edge = nullptr; + + // See if there is an edge here + for (QGraphicsItem* item : qAsConst(items)) { + new_drop_edge = dynamic_cast(item); + + if (new_drop_edge) { + drop_input_.Reset(); + + NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); + + // Determine best input to connect to our new node + if (attached_node->GetEffectInput().IsValid()) { + // If node specifies an effect input, use that immediately + drop_input_ = attached_node->GetEffectInput(); + } else { + // Otherwise, we may have to iterate to find a valid one + for (const QString& input : attached_node->inputs()) { + if (input == Node::kEnabledInput) { + // Ignore enabled input + continue; + } + + NodeInput i(attached_node, input); + + if (attached_node->IsInputConnectable(input)) { + if (attached_node->GetInputDataType(input) == drop_edge_data_type) { + // Found exactly the type we're looking for, set and break this loop + drop_input_ = i; + break; + } else if (!drop_input_.IsValid()) { + // Default to first connectable input + drop_input_ = i; + } + } + } + } + + if (new_drop_edge->input().node()->OutputsTo(attached_node, true)) { + drop_input_.Reset(); + } + + if (drop_input_.IsValid()) { + break; + } else { + new_drop_edge = nullptr; + } + } + } + + if (drop_edge_ != new_drop_edge) { + if (drop_edge_) { + drop_edge_->SetHighlighted(false); + } + + drop_edge_ = new_drop_edge; + + if (drop_edge_) { + drop_edge_->SetHighlighted(true); + } + } + } +} + +QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, Node *select_context, const QPoint &pos) +{ + QVector select_nodes; + + { + MultiUndoCommand *add_command = new MultiUndoCommand(); + + foreach (const AttachedItem &ai, attached_items_) { + if (select_context->OutputsTo(ai.node, true)) { + continue; + } + + if (ai.item) { + select_nodes.append(ai.node); + } + + if (select_context->ContextContainsNode(ai.node)) { + continue; + } + + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); + + // Add node to the context + if (ai.item) { + add_command->add_child(new NodeSetPositionCommand(ai.node, select_context, scene_.context_map().value(select_context)->MapScenePosToNodePosInContext(ai.item->pos()))); + } + } + + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } + } + + { + // Dropped attached item onto an edge, connect it between them + MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); + if (attached_items_.size() == 1) { + Node* dropping_node = nullptr; + + foreach (const AttachedItem &ai, attached_items_) { + if (ai.item && !select_context->ContextContainsNode(ai.node) && !select_context->OutputsTo(ai.node, true)) { + dropping_node = ai.node; + break; + } + } + + if (dropping_node && drop_edge_) { + // Remove old edge + drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); + + // Place new edges + drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); + drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + } + + drop_edge_ = nullptr; + } + if (drop_edge_command->child_count()) { + drop_edge_command->redo_now(); + command->add_child(drop_edge_command); + } else { + delete drop_edge_command; + } + } + + DetachItemsFromCursor(false); + + return select_nodes; +} + +Node *NodeView::GetContextAtMousePos(const QPoint &p) +{ + QList items_at_cursor = this->items(p); + foreach (QGraphicsItem *i, items_at_cursor) { + if (NodeViewContext *context_item = dynamic_cast(i)) { + return context_item->GetContext(); + } + } + + return nullptr; +} + void NodeView::ConnectSelectionChangedSignal() { connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::UpdateSelectionCache); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index e367e5b2e..15a5ddc28 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -133,6 +133,11 @@ protected: virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void mouseDoubleClickEvent(QMouseEvent* event) override; + virtual void dragEnterEvent(QDragEnterEvent *event) override; + virtual void dragMoveEvent(QDragMoveEvent *event) override; + virtual void dropEvent(QDropEvent *event) override; + virtual void dragLeaveEvent(QDragLeaveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; virtual void ZoomIntoCursorPosition(QWheelEvent *event, double multiplier, const QPointF &cursor_pos) override; @@ -149,6 +154,9 @@ private: void SetFlowDirection(NodeViewCommon::FlowDirection dir); void MoveAttachedNodesToCursor(const QPoint &p); + void ProcessMovingAttachedNodes(const QPoint &pos); + QVector ProcessDroppingAttachedNodes(MultiUndoCommand *command, Node *select_context, const QPoint &pos); + Node *GetContextAtMousePos(const QPoint &p); void ConnectSelectionChangedSignal(); void DisconnectSelectionChangedSignal(); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index d17b9a3d8..e25255497 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -55,10 +55,10 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event) QStringList mime_formats = event->GetMimeData()->formats(); // Listen for MIME data from a ProjectViewModel - if (mime_formats.contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (mime_formats.contains(Project::kItemMimeType)) { // Data is drag/drop data from a ProjectViewModel - QByteArray model_data = event->GetMimeData()->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray model_data = event->GetMimeData()->data(Project::kItemMimeType); // Use QDataStream to deserialize the data QDataStream stream(&model_data, QIODevice::ReadOnly); diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 28b182b5a..9ede7b066 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -87,7 +87,7 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl if (!streams.isEmpty()) { data_stream << streams << reinterpret_cast(GetConnectedNode()); - mimedata->setData(QStringLiteral("application/x-oliveprojectitemdata"), encoded_data); + mimedata->setData(Project::kItemMimeType, encoded_data); drag->setMimeData(mimedata); drag->exec(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 7a4e25da5..ded1dd4fe 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1514,14 +1514,14 @@ void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to) void ViewerWidget::DragEntered(QDragEnterEvent* event) { - if (event->mimeData()->formats().contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (event->mimeData()->formats().contains(Project::kItemMimeType)) { event->accept(); } } void ViewerWidget::Dropped(QDropEvent *event) { - QByteArray mimedata = event->mimeData()->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray mimedata = event->mimeData()->data(Project::kItemMimeType); QDataStream stream(&mimedata, QIODevice::ReadOnly); // Variables to deserialize into From 1809d908cd5d0580d809578f3574486df2d8c729 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 19:55:03 -0700 Subject: [PATCH 33/62] ociobase: detect removal from parent too Fixes crash when copying and pasting an OCIOBase node --- app/node/color/ociobase/ociobase.cpp | 18 +++++++++++------- app/node/color/ociobase/ociobase.h | 4 +++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp index 104390b89..831c2f8be 100644 --- a/app/node/color/ociobase/ociobase.cpp +++ b/app/node/color/ociobase/ociobase.cpp @@ -35,18 +35,14 @@ OCIOBaseNode::OCIOBaseNode() : SetEffectInput(kTextureInput); - connect(this, &Node::AddedToGraph, this, &OCIOBaseNode::ParentChanged); + connect(this, &Node::AddedToGraph, this, &OCIOBaseNode::AddedToGraph); + connect(this, &Node::RemovedFromGraph, this, &OCIOBaseNode::RemovedFromGraph); SetFlags(kVideoEffect); } -void OCIOBaseNode::ParentChanged(NodeGraph *graph) +void OCIOBaseNode::AddedToGraph(NodeGraph *graph) { - if (manager_) { - disconnect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged); - manager_ = nullptr; - } - if (Project *p = dynamic_cast(graph)) { manager_ = p->color_manager(); connect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged); @@ -54,6 +50,14 @@ void OCIOBaseNode::ParentChanged(NodeGraph *graph) } } +void OCIOBaseNode::RemovedFromGraph() +{ + if (manager_) { + disconnect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged); + manager_ = nullptr; + } +} + void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { if (value[kTextureInput].toTexture() && processor_) { diff --git a/app/node/color/ociobase/ociobase.h b/app/node/color/ociobase/ociobase.h index a80c3c1a2..2336c2214 100644 --- a/app/node/color/ociobase/ociobase.h +++ b/app/node/color/ociobase/ociobase.h @@ -51,7 +51,9 @@ private: ColorProcessorPtr processor_; private slots: - void ParentChanged(olive::NodeGraph *graph); + void AddedToGraph(NodeGraph *graph); + + void RemovedFromGraph(); }; From 6613ff3296eb3b9583f6270445f36debdd1b9cef Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 20:02:32 -0700 Subject: [PATCH 34/62] nodeview: fix drop regressions --- app/widget/nodeview/nodeview.cpp | 45 +++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index dd1aa256c..7e3f9e138 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -585,6 +585,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } if (select_context) { + DeselectAll(); scene_.context_map().value(select_context)->Select(select_nodes); } } @@ -632,10 +633,11 @@ void NodeView::dragEnterEvent(QDragEnterEvent *event) new_item = new NodeViewItem(f, nullptr); new_item->SetFlowDirection(scene_.GetFlowDirection()); + new_item->SetNodePosition(QPointF(0, y)); y++; scene_.addItem(new_item); - new_attached.append({new_item, f, QPointF(0, y)}); + new_attached.append({new_item, f, new_item->pos()}); } } @@ -671,6 +673,7 @@ void NodeView::dropEvent(QDropEvent *event) QVector select_nodes = ProcessDroppingAttachedNodes(command, drop_ctx, event->pos()); Core::instance()->undo_stack()->pushIfHasChildren(command); + DeselectAll(); scene_.context_map().value(drop_ctx)->Select(select_nodes); event->accept(); @@ -965,6 +968,7 @@ void NodeView::DetachItemsFromCursor(bool delete_nodes_too) delete ai.item; if (delete_nodes_too) { + qDebug() << "deleting" << ai.node; delete ai.node; } } @@ -1072,27 +1076,32 @@ QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, { QVector select_nodes; + // Make a copy + QVector attached = attached_items_; + + for (int i=0; iOutputsTo(ai.node, true)) { + attached.removeAt(i); + } else if (select_context->ContextContainsNode(ai.node)) { + select_nodes.append(ai.node); + attached.removeAt(i); + } + } + { MultiUndoCommand *add_command = new MultiUndoCommand(); - foreach (const AttachedItem &ai, attached_items_) { - if (select_context->OutputsTo(ai.node, true)) { - continue; - } - - if (ai.item) { - select_nodes.append(ai.node); - } - - if (select_context->ContextContainsNode(ai.node)) { - continue; - } - + foreach (const AttachedItem &ai, attached) { // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); + if (ai.node->parent() != select_context->parent()) { + add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); + } // Add node to the context if (ai.item) { + select_nodes.append(ai.node); add_command->add_child(new NodeSetPositionCommand(ai.node, select_context, scene_.context_map().value(select_context)->MapScenePosToNodePosInContext(ai.item->pos()))); } } @@ -1108,11 +1117,11 @@ QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, { // Dropped attached item onto an edge, connect it between them MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached_items_.size() == 1) { + if (attached.size() == 1) { Node* dropping_node = nullptr; - foreach (const AttachedItem &ai, attached_items_) { - if (ai.item && !select_context->ContextContainsNode(ai.node) && !select_context->OutputsTo(ai.node, true)) { + foreach (const AttachedItem &ai, attached) { + if (ai.item && !select_context->OutputsTo(ai.node, true)) { dropping_node = ai.node; break; } From 165a8edd0a6b69d4fd09cbc0523936e97896ce3f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 12 May 2022 12:00:00 -0700 Subject: [PATCH 35/62] volume: insert volume input Fixes #1924 --- app/node/audio/volume/volume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 2ec49d596..81a2306a7 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -81,6 +81,7 @@ void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, No } else { // Requires job SampleJob job(kSamplesInput, value); + job.Insert(kVolumeInput, value); table->Push(NodeValue::kSamples, QVariant::fromValue(job), this); } } From 22bd9f4eec19739e26e9363f38401bbe67e82cc3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 12 May 2022 13:29:54 -0700 Subject: [PATCH 36/62] nodeparamviewitem: fix max column Fixes #1922 --- app/widget/nodeparamview/nodeparamviewitem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 3151724ce..05e9fe932 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -40,7 +40,7 @@ const int NodeParamViewItemBody::kOptionalCheckBox = 0; const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1; const int NodeParamViewItemBody::kLabelColumn = 2; const int NodeParamViewItemBody::kWidgetStartColumn = 3; -const int NodeParamViewItemBody::kMaxWidgetColumn = kKeyControlColumn; +const int NodeParamViewItemBody::kMaxWidgetColumn = kArrayRemoveColumn; #define super NodeParamViewItemBase From 18d10f6c8de76730bf239947687dd910265f429e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 12 May 2022 13:39:59 -0700 Subject: [PATCH 37/62] widgets: unset cursor rather than setting arrow --- app/widget/nodeview/nodeviewminimap.cpp | 6 +++++- app/widget/timeruler/seekablewidget.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/widget/nodeview/nodeviewminimap.cpp b/app/widget/nodeview/nodeviewminimap.cpp index d48a6ac63..b796a7da3 100644 --- a/app/widget/nodeview/nodeviewminimap.cpp +++ b/app/widget/nodeview/nodeviewminimap.cpp @@ -109,7 +109,11 @@ void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event) EmitMoveSignal(event); } } else { - setCursor(MouseInsideResizeTriangle(event) ? Qt::SizeFDiagCursor : Qt::ArrowCursor); + if (MouseInsideResizeTriangle(event)) { + setCursor(Qt::SizeFDiagCursor); + } else { + unsetCursor(); + } } } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index e915375fd..141a28b70 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -189,7 +189,11 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) } } else if (timeline_points_) { // Look for resize points - setCursor(FindResizeHandle(event) ? Qt::SizeHorCursor : Qt::ArrowCursor); + if (FindResizeHandle(event)) { + setCursor(Qt::SizeHorCursor); + } else { + unsetCursor(); + } } } From 1def68f1a43de0e89f4e15eabd57a784c0948066 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 12 May 2022 13:40:11 -0700 Subject: [PATCH 38/62] viewer: allow adding objects with a rect --- app/widget/timelinewidget/tool/add.cpp | 134 ++++++++++++++----------- app/widget/timelinewidget/tool/add.h | 2 + app/widget/viewer/viewer.cpp | 45 +++++++++ app/widget/viewer/viewer.h | 2 + app/widget/viewer/viewerdisplay.cpp | 35 ++++++- app/widget/viewer/viewerdisplay.h | 11 +- 6 files changed, 167 insertions(+), 62 deletions(-) diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 364863ef7..9ce8994b4 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -25,6 +25,7 @@ #include "node/generator/shape/shapenode.h" #include "node/generator/solid/solid.h" #include "node/generator/text/textv3.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/timelinewidget/timelinewidget.h" #include "widget/timelinewidget/undo/timelineundopointer.h" @@ -94,8 +95,6 @@ void AddTool::MouseMove(TimelineViewMouseEvent *event) void AddTool::MouseRelease(TimelineViewMouseEvent *event) { - const Track::Reference& track = ghost_->GetTrack(); - if (ghost_) { if (!ghost_->GetAdjustedLength().isNull()) { MultiUndoCommand* command = new MultiUndoCommand(); @@ -104,62 +103,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(subtitle_section_command); } - ClipBlock* clip; - if (Core::instance()->GetSelectedAddableObject() == olive::Tool::kAddableSubtitle) { - clip = new SubtitleBlock(); - } else { - clip = new ClipBlock(); - } - clip->set_length_and_media_out(ghost_->GetAdjustedLength()); - clip->SetLabel(olive::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); - - NodeGraph* graph = static_cast(parent()->GetConnectedNode()->parent()); - - command->add_child(new NodeAddCommand(graph, clip)); - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); - command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), - track.index(), - clip, - ghost_->GetAdjustedIn())); - - Node *node_to_add = nullptr; - - switch (Core::instance()->GetSelectedAddableObject()) { - case olive::Tool::kAddableEmpty: - // Empty, nothing to be done - break; - case olive::Tool::kAddableSolid: - { - node_to_add = new SolidGenerator(); - break; - } - case olive::Tool::kAddableShape: - node_to_add = new ShapeNode(); - break; - case olive::Tool::kAddableTitle: - { - node_to_add = new TextGeneratorV3(); - break; - } - case olive::Tool::kAddableBars: - case olive::Tool::kAddableTone: - // Not implemented yet - qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject(); - break; - case olive::Tool::kAddableSubtitle: - // The block itself is the node we want - break; - case olive::Tool::kAddableCount: - // Invalid value, do nothing - break; - } - - if (node_to_add) { - QPointF extra_node_offset(kDefaultDistanceFromOutput, 0); - command->add_child(new NodeAddCommand(graph, node_to_add)); - command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); - } + CreateAddableClip(command, parent()->sequence(), ghost_->GetTrack(), ghost_->GetAdjustedIn(), ghost_->GetAdjustedLength()); Core::instance()->undo_stack()->push(command); } @@ -170,6 +114,80 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) } } +Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, const Track::Reference &track, const rational &in, const rational &length, const QRectF &rect) +{ + ClipBlock* clip; + if (Core::instance()->GetSelectedAddableObject() == olive::Tool::kAddableSubtitle) { + clip = new SubtitleBlock(); + } else { + clip = new ClipBlock(); + } + clip->set_length_and_media_out(length); + clip->SetLabel(olive::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); + + NodeGraph* graph = sequence->parent(); + + command->add_child(new NodeAddCommand(graph, clip)); + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); + command->add_child(new TrackPlaceBlockCommand(sequence->track_list(track.type()), + track.index(), + clip, + in)); + + Node *node_to_add = nullptr; + + switch (Core::instance()->GetSelectedAddableObject()) { + case olive::Tool::kAddableEmpty: + // Empty, nothing to be done + break; + case olive::Tool::kAddableSolid: + { + node_to_add = new SolidGenerator(); + break; + } + case olive::Tool::kAddableShape: + node_to_add = new ShapeNode(); + break; + case olive::Tool::kAddableTitle: + { + node_to_add = new TextGeneratorV3(); + break; + } + case olive::Tool::kAddableBars: + case olive::Tool::kAddableTone: + // Not implemented yet + qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject(); + break; + case olive::Tool::kAddableSubtitle: + // The block itself is the node we want + break; + case olive::Tool::kAddableCount: + // Invalid value, do nothing + break; + } + + if (node_to_add) { + QPointF extra_node_offset(kDefaultDistanceFromOutput, 0); + command->add_child(new NodeAddCommand(graph, node_to_add)); + command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); + + if (!rect.isNull()) { + if (ShapeNodeBase *snb = dynamic_cast(node_to_add)) { + NodeInput pos(snb, ShapeNodeBase::kPositionInput); + NodeInput sz(snb, ShapeNodeBase::kSizeInput); + + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 0), rect.width())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 1), rect.height())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 0), rect.x())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 1), rect.y())); + } + } + } + + return node_to_add; +} + void AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards) { // Calculate movement diff --git a/app/widget/timelinewidget/tool/add.h b/app/widget/timelinewidget/tool/add.h index 025d10e12..0320fddd8 100644 --- a/app/widget/timelinewidget/tool/add.h +++ b/app/widget/timelinewidget/tool/add.h @@ -34,6 +34,8 @@ public: virtual void MouseMove(TimelineViewMouseEvent *event) override; virtual void MouseRelease(TimelineViewMouseEvent *event) override; + static Node *CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, const Track::Reference &track, const rational &in, const rational &length, const QRectF &rect = QRectF()); + protected: void MouseMoveInternal(const rational& cursor_frame, bool outwards); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index ded1dd4fe..be7e5f9ce 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -37,12 +37,14 @@ #include "common/timecodefunctions.h" #include "config/config.h" #include "core.h" +#include "node/block/gap/gap.h" #include "node/project/project.h" #include "render/rendermanager.h" #include "task/taskmanager.h" #include "viewerpreventsleep.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" +#include "widget/timelinewidget/tool/add.h" #include "widget/timeruler/timeruler.h" namespace olive { @@ -93,6 +95,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(display_widget_, &ViewerDisplayWidget::Dropped, this, &ViewerWidget::Dropped); connect(display_widget_, &ViewerDisplayWidget::TextureChanged, this, &ViewerWidget::TextureChanged); connect(display_widget_, &ViewerDisplayWidget::QueueStarved, this, &ViewerWidget::ForceRequeueFromCurrentTime); + connect(display_widget_, &ViewerDisplayWidget::CreateAddableAt, this, &ViewerWidget::CreateAddableAt); connect(sizer_, &ViewerSizer::RequestScale, display_widget_, &ViewerDisplayWidget::SetMatrixZoom); connect(sizer_, &ViewerSizer::RequestTranslate, display_widget_, &ViewerDisplayWidget::SetMatrixTranslate); connect(display_widget_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove); @@ -385,6 +388,7 @@ void ViewerWidget::CacheSequenceInOut() void ViewerWidget::SetGizmos(Node *node) { + qDebug() << "setting gizmos to" << node; display_widget_->SetTimeTarget(GetConnectedNode()); display_widget_->SetGizmos(node); } @@ -476,6 +480,47 @@ void ViewerWidget::UpdateAudioProcessor() } } +void ViewerWidget::CreateAddableAt(QRectF f) +{ + if (Sequence *s = dynamic_cast(GetConnectedNode())) { + Track::Type type = Track::kVideo; + int track_index = -1; + TrackList *list = s->track_list(type); + const rational &in = GetTime(); + rational length = OLIVE_CONFIG("DefaultStillLength").value(); + rational out = in + length; + + // Find a free track where we won't overwrite anything + while (true) { + track_index++; + + if (track_index >= list->GetTrackCount()) { + // Just create a new track + break; + } + + Track *track = list->GetTrackAt(track_index); + if (track->IsLocked()) { + continue; + } + + Block *b = track->NearestBlockBeforeOrAt(in); + if (!b || (dynamic_cast(b) && b->out() >= out)) { + break; + } + } + + // Normalize around center of sequence + f.translate(-s->GetVideoParams().width()*0.5, -s->GetVideoParams().height()*0.5); + f.translate(f.width()*0.5, f.height()*0.5); + + MultiUndoCommand *command = new MultiUndoCommand(); + Node *clip = AddTool::CreateAddableClip(command, s, Track::Reference(type, track_index), in, length, f); + Core::instance()->undo_stack()->pushIfHasChildren(command); + SetGizmos(clip); + } +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 3a297fbc8..f251edcac 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -325,6 +325,8 @@ private slots: void UpdateAudioProcessor(); + void CreateAddableAt(QRectF f); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 76c5c09e2..376aea676 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -67,9 +67,10 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : show_fps_(false), frames_skipped_(0), show_widget_background_(false), - push_mode_(kPushNull) + push_mode_(kPushNull), + add_band_(nullptr) { - connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::UpdateCursor); + connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged); connect(this, &ViewerDisplayWidget::InnerWidgetMouseMove, this, &ViewerDisplayWidget::EmitColorAtCursor); @@ -105,6 +106,8 @@ void ViewerDisplayWidget::UpdateCursor() { if (Core::instance()->tool() == Tool::kHand) { setCursor(Qt::OpenHandCursor); + } else if (Core::instance()->tool() == Tool::kAdd) { + setCursor(Qt::CrossCursor); } else { unsetCursor(); } @@ -136,6 +139,11 @@ void ViewerDisplayWidget::SetBlank() update(); } +void ViewerDisplayWidget::ToolChanged() +{ + UpdateCursor(); +} + void ViewerDisplayWidget::SetDeinterlacing(bool e) { deinterlace_ = e; @@ -235,7 +243,16 @@ void ViewerDisplayWidget::IncrementSkippedFrames() void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton && gizmos_ + if (event->button() == Qt::LeftButton && Core::instance()->tool() == Tool::kAdd + && (Core::instance()->GetSelectedAddableObject() == Tool::kAddableShape || Core::instance()->GetSelectedAddableObject() == Tool::kAddableTitle)) { + + add_band_start_ = event->pos(); + + add_band_ = new QRubberBand(QRubberBand::Rectangle, this); + add_band_->setGeometry(QRect(add_band_start_, add_band_start_)); + add_band_->show(); + + } else if (event->button() == Qt::LeftButton && gizmos_ && (current_gizmo_ = TryGizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos())))) { // Handle gizmo click @@ -274,6 +291,10 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) hand_last_drag_pos_ = event->pos(); + } else if (add_band_) { + + add_band_->setGeometry(QRect(event->pos(), add_band_start_).normalized()); + } else if (current_gizmo_) { // Signal movement @@ -324,6 +345,14 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) hand_dragging_ = false; UpdateCursor(); + } else if (add_band_) { + + QRectF r = GenerateGizmoTransform().inverted().mapRect(add_band_->geometry()); + emit CreateAddableAt(r); + + add_band_->deleteLater(); + add_band_ = nullptr; + } else if (current_gizmo_) { // Handle gizmo diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index a8c993aa1..54320d33f 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -21,8 +21,9 @@ #ifndef VIEWERGLWIDGET_H #define VIEWERGLWIDGET_H -#include #include +#include +#include #include "node/color/colormanager/colormanager.h" #include "node/gizmo/text.h" @@ -163,6 +164,8 @@ public slots: */ void UpdateCursor(); + void ToolChanged(); + /** * @brief Enables/disables a basic deinterlace on the viewer */ @@ -208,6 +211,8 @@ signals: void QueueStarved(); + void CreateAddableAt(const QRectF &rect); + protected: /** * @brief Override the mouse press event for the DragStarted() signal and gizmos @@ -371,6 +376,9 @@ private: rational playback_timebase_; + QRubberBand *add_band_; + QPoint add_band_start_; + private slots: void EmitColorAtCursor(QMouseEvent* e); @@ -380,6 +388,7 @@ private slots: void SubtitlesChanged(const TimeRange &r); + }; } From b153834878c97dabe8873fafaafd48f197bb7817 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 12 May 2022 13:56:10 -0700 Subject: [PATCH 39/62] viewer: skip making object if rect too small --- app/widget/viewer/viewerdisplay.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 376aea676..bc5ab4450 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -347,8 +347,11 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) } else if (add_band_) { - QRectF r = GenerateGizmoTransform().inverted().mapRect(add_band_->geometry()); - emit CreateAddableAt(r); + const QRect &band_rect = add_band_->geometry(); + if (band_rect.width() > 1 && band_rect.height() > 1) { + QRectF r = GenerateGizmoTransform().inverted().mapRect(add_band_->geometry()); + emit CreateAddableAt(r); + } add_band_->deleteLater(); add_band_ = nullptr; From b42c57acac95ffb61260a1af5f33e04e2c4d63fd Mon Sep 17 00:00:00 2001 From: Simran Spiller Date: Thu, 12 May 2022 23:28:37 +0200 Subject: [PATCH 40/62] docker: Set C++17 for OTIO --- .github/workflows/ci.yml | 2 +- docker/README.md | 12 ++++++------ docker/ci-olive/Dockerfile | 2 +- docker/ci-otio/Dockerfile | 3 +++ docker/scripts/build_otio.sh | 1 + 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 992be23ca..7bd345ed3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: ${{ matrix.cmake-gen }}> runs-on: ubuntu-latest container: - image: olivevideoeditor/ci-olive:2022.1 + image: olivevideoeditor/ci-olive:2022.2 steps: - name: Checkout Source Code diff --git a/docker/README.md b/docker/README.md index c5a824194..fa0c08423 100644 --- a/docker/README.md +++ b/docker/README.md @@ -27,14 +27,14 @@ docker pull olivevideoeditor/ci-package-otio:0.14.1 docker pull olivevideoeditor/ci-package-crashpad docker pull olivevideoeditor/ci-package-ffmpeg:5.0 docker pull olivevideoeditor/ci-package-ocio:2022-2.1.1 -docker pull olivevideoeditor/ci-olive:2022.1 +docker pull olivevideoeditor/ci-olive:2022.2 ``` Use `ci-olive` image as local build container, by mounting working copy at `~/olive` into guest system at `/opt/olive/olive`: ```bash -docker run --rm -it -v ~/olive:/opt/olive/olive olivevideoeditor/ci-olive:2022.1 +docker run --rm -it -v ~/olive:/opt/olive/olive olivevideoeditor/ci-olive:2022.2 mkdir build cd build cmake .. -G Ninja @@ -50,11 +50,11 @@ docker build -t olivevideoeditor/ci-package-otio:0.14.1 -f ci-otio/Dockerfile . docker build -t olivevideoeditor/ci-package-crashpad -f ci-crashpad/Dockerfile . docker build -t olivevideoeditor/ci-package-ffmpeg:5.0 -f ci-ffmpeg/Dockerfile . docker build -t olivevideoeditor/ci-package-ocio:2022-2.1.1 -f ci-ocio/Dockerfile . -docker build -t olivevideoeditor/ci-olive:2022.1 -f ci-olive/Dockerfile . +docker build -t olivevideoeditor/ci-olive:2022.2 -f ci-olive/Dockerfile . ``` -Note that `2022` in `ci-olive:2022.1` stands for the -[VFX Reference Platform](http://vfxplatform.com/) calendar year and `1` for the +Note that `2022` in `ci-olive:2022.2` stands for the +[VFX Reference Platform](http://vfxplatform.com/) calendar year and `2` for the build image revision (should be incremented each time a new image is published). Publish images: @@ -65,5 +65,5 @@ docker push olivevideoeditor/ci-package-otio:0.14.1 docker push olivevideoeditor/ci-package-crashpad docker push olivevideoeditor/ci-package-ffmpeg:5.0 docker push olivevideoeditor/ci-package-ocio:2022-2.1.1 -docker push olivevideoeditor/ci-olive:2022.1 +docker push olivevideoeditor/ci-olive:2022.2 ``` diff --git a/docker/ci-olive/Dockerfile b/docker/ci-olive/Dockerfile index b4fb4ac11..fab98dfc0 100644 --- a/docker/ci-olive/Dockerfile +++ b/docker/ci-olive/Dockerfile @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later # Build image (default): -# docker build -t olivevideoeditor/ci-olive:2022.1 -f ci-olive/Dockerfile . +# docker build -t olivevideoeditor/ci-olive:2022.2 -f ci-olive/Dockerfile . # # .n is the build image revision number. It should be incremented each time a # new image is published (also update the GitHub Actions workflow!). diff --git a/docker/ci-otio/Dockerfile b/docker/ci-otio/Dockerfile index 1c2bdc94f..794b5797d 100644 --- a/docker/ci-otio/Dockerfile +++ b/docker/ci-otio/Dockerfile @@ -9,6 +9,7 @@ ARG ASWF_PKG_ORG=aswftesting ARG CI_COMMON_VERSION=2 ARG VFXPLATFORM_VERSION=2022 ARG OTIO_VERSION=v0.14.1 +ARG CXX_STANDARD=17 # We are currently not interested in the Python implementation and bindings #FROM ${ASWF_PKG_ORG}/ci-package-python:${VFXPLATFORM_VERSION} as ci-package-python @@ -20,6 +21,7 @@ FROM ${OLIVE_ORG}/ci-common:${CI_COMMON_VERSION} as ci-otio ARG OLIVE_ORG ARG VFXPLATFORM_VERSION ARG OTIO_VERSION +ARG CXX_STANDARD ARG PYTHON_VERSION=3.9 LABEL maintainer="olivevideoeditor@gmail.com" @@ -36,6 +38,7 @@ ENV OLIVE_ORG=${OLIVE_ORG} \ CI_COMMON_VERSION=${CI_COMMON_VERSION} \ VFXPLATFORM_VERSION=${VFXPLATFORM_VERSION} \ OTIO_VERSION=${OTIO_VERSION} \ + CXX_STANDARD=${CXX_STANDARD} \ OLIVE_INSTALL_PREFIX=/usr/local # PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages:/usr/local/lib/python \ diff --git a/docker/scripts/build_otio.sh b/docker/scripts/build_otio.sh index b232e35ab..56743a411 100644 --- a/docker/scripts/build_otio.sh +++ b/docker/scripts/build_otio.sh @@ -13,6 +13,7 @@ mkdir build cd build cmake .. -G "Ninja" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_CXX_STANDARD="${CXX_STANDARD}" \ -DCMAKE_INSTALL_PREFIX="${OLIVE_INSTALL_PREFIX}" \ -DOTIO_PYTHON_INSTALL=OFF cmake --build . From b63b792d5adfd941fe0d64895334e7ea83d590d3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 13 May 2022 08:40:49 -0700 Subject: [PATCH 41/62] nodeparamview: added icon to "edit text in viewer" button --- app/ui/icons/icons.cpp | 3 +++ app/ui/icons/icons.h | 1 + app/widget/nodeparamview/nodeparamviewtextedit.cpp | 1 + 3 files changed, 5 insertions(+) diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 5b961f96e..581235e37 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -91,6 +91,7 @@ QIcon icon::EyeOpened; QIcon icon::EyeClosed; QIcon icon::LockOpened; QIcon icon::LockClosed; +QIcon icon::Pencil; void icon::LoadAll(const QString& theme) { @@ -161,6 +162,8 @@ void icon::LoadAll(const QString& theme) EyeClosed = Create(theme, "eye-closed"); LockOpened = Create(theme, "lock-opened"); LockClosed = Create(theme, "lock-closed"); + + Pencil = Create(theme, "text-edit"); } QIcon icon::Create(const QString& theme, const QString &name) diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index 42711676a..c331a1683 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -103,6 +103,7 @@ extern QIcon EyeOpened; extern QIcon EyeClosed; extern QIcon LockOpened; extern QIcon LockClosed; +extern QIcon Pencil; /** * @brief Create an icon object loaded from file diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index e9423f360..6e3b291cc 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -45,6 +45,7 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : connect(edit_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::ShowTextDialog); edit_in_viewer_btn_ = new QPushButton(tr("Edit In Viewer")); + edit_in_viewer_btn_->setIcon(icon::Pencil); layout->addWidget(edit_in_viewer_btn_); connect(edit_in_viewer_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::RequestEditInViewer); From 07f92911cda6ce97190011ee098541f6bd4cd872 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 13 May 2022 08:41:14 -0700 Subject: [PATCH 42/62] text: minor refactoring to make rect setting code more reusable --- app/node/generator/shape/shapenodebase.cpp | 16 +++++++ app/node/generator/shape/shapenodebase.h | 2 + app/widget/timelinewidget/tool/add.cpp | 56 +++++++++++----------- app/widget/viewer/viewer.cpp | 14 +++--- app/widget/viewer/viewer.h | 2 +- 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index e7467f9f4..582725ec4 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -25,6 +25,7 @@ #include "common/util.h" #include "core.h" +#include "widget/nodeparamview/nodeparamviewundo.h" namespace olive { @@ -102,6 +103,21 @@ void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlob poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt)); } +void ShapeNodeBase::SetRect(QRectF rect, const VideoParams &sequence_res, MultiUndoCommand *command) +{ + // Normalize around center of sequence + rect.translate(-sequence_res.width()*0.5, -sequence_res.height()*0.5); + rect.translate(rect.width()*0.5, rect.height()*0.5); + + NodeInput pos(this, ShapeNodeBase::kPositionInput); + NodeInput sz(this, ShapeNodeBase::kSizeInput); + + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 0), rect.width())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 1), rect.height())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 0), rect.x())); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 1), rect.y())); +} + void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) { DraggableGizmo *gizmo = static_cast(sender()); diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index cf48be8ae..fd0f6658a 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -39,6 +39,8 @@ public: virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + void SetRect(QRectF rect, const VideoParams &sequence_res, MultiUndoCommand *command); + static const QString kPositionInput; static const QString kSizeInput; static const QString kColorInput; diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 9ce8994b4..2dc04f17a 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -50,22 +50,22 @@ void AddTool::MousePress(TimelineViewMouseEvent *event) Track::Type add_type = Track::kNone; switch (Core::instance()->GetSelectedAddableObject()) { - case olive::Tool::kAddableBars: - case olive::Tool::kAddableSolid: - case olive::Tool::kAddableTitle: - case olive::Tool::kAddableShape: + case Tool::kAddableBars: + case Tool::kAddableSolid: + case Tool::kAddableTitle: + case Tool::kAddableShape: add_type = Track::kVideo; break; - case olive::Tool::kAddableTone: + case Tool::kAddableTone: add_type = Track::kAudio; break; - case olive::Tool::kAddableSubtitle: + case Tool::kAddableSubtitle: add_type = Track::kSubtitle; break; - case olive::Tool::kAddableEmpty: + case Tool::kAddableEmpty: // Leave as "none", which means this block can be placed on any track break; - case olive::Tool::kAddableCount: + case Tool::kAddableCount: // Return so we do nothing return; } @@ -103,7 +103,15 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(subtitle_section_command); } - CreateAddableClip(command, parent()->sequence(), ghost_->GetTrack(), ghost_->GetAdjustedIn(), ghost_->GetAdjustedLength()); + Sequence *s = parent()->sequence(); + + // If we want to set a manual rect for something, we can do so here + // + //VideoParams svp = s->GetVideoParams(); + //QRectF r(0, 0, svp.width(), svp.height()); + //r.adjust(svp.width()/10, svp.height()/10, -svp.width()/10, -svp.height()/10); + + CreateAddableClip(command, s, ghost_->GetTrack(), ghost_->GetAdjustedIn(), ghost_->GetAdjustedLength()); Core::instance()->undo_stack()->push(command); } @@ -137,31 +145,27 @@ Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, Node *node_to_add = nullptr; switch (Core::instance()->GetSelectedAddableObject()) { - case olive::Tool::kAddableEmpty: + case Tool::kAddableEmpty: // Empty, nothing to be done break; - case olive::Tool::kAddableSolid: - { + case Tool::kAddableSolid: node_to_add = new SolidGenerator(); break; - } - case olive::Tool::kAddableShape: + case Tool::kAddableShape: node_to_add = new ShapeNode(); break; - case olive::Tool::kAddableTitle: - { + case Tool::kAddableTitle: node_to_add = new TextGeneratorV3(); break; - } - case olive::Tool::kAddableBars: - case olive::Tool::kAddableTone: + case Tool::kAddableBars: + case Tool::kAddableTone: // Not implemented yet qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject(); break; - case olive::Tool::kAddableSubtitle: + case Tool::kAddableSubtitle: // The block itself is the node we want break; - case olive::Tool::kAddableCount: + case Tool::kAddableCount: // Invalid value, do nothing break; } @@ -173,14 +177,8 @@ Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); if (!rect.isNull()) { - if (ShapeNodeBase *snb = dynamic_cast(node_to_add)) { - NodeInput pos(snb, ShapeNodeBase::kPositionInput); - NodeInput sz(snb, ShapeNodeBase::kSizeInput); - - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 0), rect.width())); - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(sz, 1), rect.height())); - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 0), rect.x())); - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(pos, 1), rect.y())); + if (ShapeNodeBase *shape = dynamic_cast(node_to_add)) { + shape->SetRect(rect, sequence->GetVideoParams(), command); } } } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index be7e5f9ce..67a76bd46 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -38,6 +38,7 @@ #include "config/config.h" #include "core.h" #include "node/block/gap/gap.h" +#include "node/generator/shape/shapenodebase.h" #include "node/project/project.h" #include "render/rendermanager.h" #include "task/taskmanager.h" @@ -480,7 +481,7 @@ void ViewerWidget::UpdateAudioProcessor() } } -void ViewerWidget::CreateAddableAt(QRectF f) +void ViewerWidget::CreateAddableAt(const QRectF &f) { if (Sequence *s = dynamic_cast(GetConnectedNode())) { Track::Type type = Track::kVideo; @@ -510,12 +511,13 @@ void ViewerWidget::CreateAddableAt(QRectF f) } } - // Normalize around center of sequence - f.translate(-s->GetVideoParams().width()*0.5, -s->GetVideoParams().height()*0.5); - f.translate(f.width()*0.5, f.height()*0.5); - MultiUndoCommand *command = new MultiUndoCommand(); - Node *clip = AddTool::CreateAddableClip(command, s, Track::Reference(type, track_index), in, length, f); + Node *clip = AddTool::CreateAddableClip(command, s, Track::Reference(type, track_index), in, length); + + if (ShapeNodeBase *shape = dynamic_cast(clip)) { + shape->SetRect(f, s->GetVideoParams(), command); + } + Core::instance()->undo_stack()->pushIfHasChildren(command); SetGizmos(clip); } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index f251edcac..4e574f179 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -325,7 +325,7 @@ private slots: void UpdateAudioProcessor(); - void CreateAddableAt(QRectF f); + void CreateAddableAt(const QRectF &f); }; From 912dcfdb3b05d35054ba939eb165355766bb03af Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 13 May 2022 09:52:28 -0700 Subject: [PATCH 43/62] timeline: allow dropping on trackviewitem to overwrite at 0 Fixes #1928 --- app/widget/timelinewidget/timelinewidget.cpp | 5 +++++ .../timelinewidget/trackview/trackview.cpp | 3 +++ .../timelinewidget/trackview/trackview.h | 4 ++++ .../trackview/trackviewitem.cpp | 22 +++++++++++++++++++ .../timelinewidget/trackview/trackviewitem.h | 10 +++++++++ .../view/timelineviewmouseevent.h | 1 + 6 files changed, 45 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 47c704a76..d46a35e9c 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -157,6 +157,11 @@ TimelineWidget::TimelineWidget(QWidget *parent) : connect(view, &TimelineView::DragLeft, this, &TimelineWidget::ViewDragLeft); connect(view, &TimelineView::DragDropped, this, &TimelineWidget::ViewDragDropped); + TrackView *tv = tview->track_view(); + connect(tv, &TrackView::DragEntered, this, &TimelineWidget::ViewDragEntered); + connect(tv, &TrackView::DragLeft, this, &TimelineWidget::ViewDragLeft); + connect(tv, &TrackView::DragDropped, this, &TimelineWidget::ViewDragDropped); + connect(tview->splitter(), &QSplitter::splitterMoved, this, &TimelineWidget::UpdateHorizontalSplitters); // Connect each view's scroll to each other diff --git a/app/widget/timelinewidget/trackview/trackview.cpp b/app/widget/timelinewidget/trackview/trackview.cpp index 562c68d55..09d2e0fbc 100644 --- a/app/widget/timelinewidget/trackview/trackview.cpp +++ b/app/widget/timelinewidget/trackview/trackview.cpp @@ -124,6 +124,9 @@ void TrackView::InsertTrack(Track *track) TrackViewItem *tvi = new TrackViewItem(track); connect(tvi, &TrackViewItem::AboutToDeleteTrack, this, &TrackView::AboutToDeleteTrack); + connect(tvi, &TrackViewItem::DragEntered, this, &TrackView::DragEntered); + connect(tvi, &TrackViewItem::DragLeft, this, &TrackView::DragLeft); + connect(tvi, &TrackViewItem::DragDropped, this, &TrackView::DragDropped); splitter_->Insert(track->Index(), track->GetTrackHeightInPixels(), diff --git a/app/widget/timelinewidget/trackview/trackview.h b/app/widget/timelinewidget/trackview/trackview.h index 1498a526b..9d56dabe0 100644 --- a/app/widget/timelinewidget/trackview/trackview.h +++ b/app/widget/timelinewidget/trackview/trackview.h @@ -43,6 +43,10 @@ public: signals: void AboutToDeleteTrack(Track *track); + void DragEntered(TimelineViewMouseEvent* event); + void DragLeft(QDragLeaveEvent* event); + void DragDropped(TimelineViewMouseEvent* event); + protected: virtual void resizeEvent(QResizeEvent *e) override; diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index 6a5ba698e..f3c254178 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -76,11 +76,33 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) : setMinimumHeight(mute_button_->height()); setContextMenuPolicy(Qt::CustomContextMenu); + setAcceptDrops(true); connect(track, &Track::MutedChanged, mute_button_, &QPushButton::setChecked); connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu); } +void TrackViewItem::dragEnterEvent(QDragEnterEvent *event) +{ + TimelineViewMouseEvent e(0, 1, 1, track_->ToReference(), Qt::NoButton, event->keyboardModifiers()); + e.SetMimeData(event->mimeData()); + e.SetEvent(event); + emit DragEntered(&e); +} + +void TrackViewItem::dragLeaveEvent(QDragLeaveEvent *event) +{ + emit DragLeft(event); +} + +void TrackViewItem::dropEvent(QDropEvent *event) +{ + TimelineViewMouseEvent e(0, 1, 1, track_->ToReference(), Qt::NoButton, event->keyboardModifiers()); + e.SetMimeData(event->mimeData()); + e.SetEvent(event); + emit DragDropped(&e); +} + QPushButton *TrackViewItem::CreateMSLButton(const QColor& checked_color) const { QPushButton* button = new QPushButton(); diff --git a/app/widget/timelinewidget/trackview/trackviewitem.h b/app/widget/timelinewidget/trackview/trackviewitem.h index 7ec2eb750..b4010cab0 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.h +++ b/app/widget/timelinewidget/trackview/trackviewitem.h @@ -28,6 +28,7 @@ #include "node/output/track/track.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/focusablelineedit/focusablelineedit.h" +#include "widget/timelinewidget/view/timelineviewmouseevent.h" namespace olive { @@ -41,6 +42,15 @@ public: signals: void AboutToDeleteTrack(Track *track); + void DragEntered(TimelineViewMouseEvent* event); + void DragLeft(QDragLeaveEvent* event); + void DragDropped(TimelineViewMouseEvent* event); + +protected: + virtual void dragEnterEvent(QDragEnterEvent *event) override; + virtual void dragLeaveEvent(QDragLeaveEvent *event) override; + virtual void dropEvent(QDropEvent *event) override; + private: QPushButton* CreateMSLButton(const QColor &checked_color) const; diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.h b/app/widget/timelinewidget/view/timelineviewmouseevent.h index 84cb3c0ba..babce9881 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.h +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.h @@ -21,6 +21,7 @@ #ifndef TIMELINEVIEWMOUSEEVENT_H #define TIMELINEVIEWMOUSEEVENT_H +#include #include #include #include From ae8ab7ce3ba9bb8dce9a75069b88455dd13f287b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 13 May 2022 12:35:59 -0700 Subject: [PATCH 44/62] viewer: remove unnecessary debug line --- app/widget/viewer/viewer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 67a76bd46..7a48a4e53 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -389,7 +389,6 @@ void ViewerWidget::CacheSequenceInOut() void ViewerWidget::SetGizmos(Node *node) { - qDebug() << "setting gizmos to" << node; display_widget_->SetTimeTarget(GetConnectedNode()); display_widget_->SetGizmos(node); } From fb72ca4997e4ada5ca4eef7198668ff7f27a3f52 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 13 May 2022 20:34:29 -0700 Subject: [PATCH 45/62] timeline: fix bug in trackviewitem drag/drop --- app/widget/timelinewidget/tool/import.cpp | 16 ++++++++++++++-- app/widget/timelinewidget/tool/import.h | 2 ++ .../timelinewidget/trackview/trackviewitem.cpp | 1 + .../timelinewidget/view/timelineviewmouseevent.h | 8 +++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index e25255497..a9198ec38 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -87,8 +87,14 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event) } } - PrepGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_), - drag_start_.GetTrack().index()); + // Create a reasonable amount of space to inset the cursor by when importing + ghost_offset_ = drag_start_.GetFrame(); + + if (!event->GetBypassImportBuffer()) { + ghost_offset_ -= parent()->SceneToTime(import_pre_buffer_); + } + + PrepGhosts(ghost_offset_, drag_start_.GetTrack().index()); if (parent()->HasGhosts() || !parent()->GetConnectedNode()) { event->accept(); @@ -107,6 +113,12 @@ void ImportTool::DragMove(TimelineViewMouseEvent *event) if (parent()->HasGhosts()) { rational time_movement = event->GetFrame() - drag_start_.GetFrame(); + + // Keep ghost offset no lower than 0 + if (ghost_offset_ + time_movement < 0) { + time_movement = -ghost_offset_; + } + int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index(); time_movement = ValidateTimeMovement(time_movement); diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index 2640f2708..565d8f0ce 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -60,6 +60,8 @@ private: int import_pre_buffer_; + rational ghost_offset_; + }; } diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index f3c254178..5eafc077d 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -87,6 +87,7 @@ void TrackViewItem::dragEnterEvent(QDragEnterEvent *event) TimelineViewMouseEvent e(0, 1, 1, track_->ToReference(), Qt::NoButton, event->keyboardModifiers()); e.SetMimeData(event->mimeData()); e.SetEvent(event); + e.SetBypassImportBuffer(true); emit DragEntered(&e); } diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.h b/app/widget/timelinewidget/view/timelineviewmouseevent.h index babce9881..79bd99d85 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.h +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.h @@ -47,7 +47,8 @@ public: button_(button), modifiers_(modifiers), source_event_(nullptr), - mime_data_(nullptr) + mime_data_(nullptr), + bypass_import_buffer_(false) { } @@ -117,6 +118,9 @@ public: source_event_->ignore(); } + bool GetBypassImportBuffer() const { return bypass_import_buffer_; } + void SetBypassImportBuffer(bool e) { bypass_import_buffer_ = e; } + private: qreal scene_x_; double scale_x_; @@ -132,6 +136,8 @@ private: const QMimeData* mime_data_; + bool bypass_import_buffer_; + }; } From c75ee6014c31d81933fdc24b5ce3ed557daea68f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 14 May 2022 08:56:06 -0700 Subject: [PATCH 46/62] ffmpeg: bypass ass encoding for srt Fixes #1927 --- app/codec/ffmpeg/ffmpegdecoder.cpp | 60 ++++++++++-------------------- app/codec/ffmpeg/ffmpegdecoder.h | 2 + app/codec/ffmpeg/ffmpegencoder.cpp | 39 ++----------------- 3 files changed, 26 insertions(+), 75 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index caf9d6857..1da0881ef 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -425,34 +425,13 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn Instance instance; instance.Open(filename_c, avstream->index); - //qDebug() << instance.GetSubtitleHeader(); + while (instance.GetPacket(pkt) >= 0) { + TimeRange time(Timecode::timestamp_to_time(pkt->pts, avstream->time_base), + Timecode::timestamp_to_time(pkt->pts + pkt->duration, avstream->time_base)); - AVSubtitle avsub; - while (instance.GetSubtitle(pkt, &avsub) >= 0) { - for (unsigned int j=0; jass; + QString text = QString::fromUtf8((const char *) pkt->data, pkt->size); - int comma = 0; - for (int k=0; kpts, avstream->time_base), - Timecode::timestamp_to_time(pkt->pts + pkt->duration, avstream->time_base)); - - sub.push_back(Subtitle(time, ass)); - } - avsubtitle_free(&avsub); + sub.push_back(Subtitle(time, text)); } instance.Close(); @@ -1132,13 +1111,7 @@ int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) { // Find next packet in the correct stream index - do { - // Free buffer in packet if there is one - av_packet_unref(pkt); - - // Read packet from file - ret = av_read_frame(fmt_ctx_, pkt); - } while (pkt->stream_index != avstream_->index && ret >= 0); + ret = GetPacket(pkt); if (ret == AVERROR_EOF) { // Don't break so that receive gets called again, but don't try to read again @@ -1172,13 +1145,7 @@ const char *FFmpegDecoder::Instance::GetSubtitleHeader() const int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) { - int ret; - - do { - av_packet_unref(pkt); - - ret = av_read_frame(fmt_ctx_, pkt); - } while (pkt->stream_index != avstream_->index && ret >= 0); + int ret = GetPacket(pkt); if (ret >= 0) { int got_sub; @@ -1191,6 +1158,19 @@ int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) return ret; } +int FFmpegDecoder::Instance::GetPacket(AVPacket *pkt) +{ + int ret; + + do { + av_packet_unref(pkt); + + ret = av_read_frame(fmt_ctx_, pkt); + } while (pkt->stream_index != avstream_->index && ret >= 0); + + return ret; +} + void FFmpegDecoder::Instance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 13535cf0f..3d8c56f97 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -95,6 +95,8 @@ private: int GetSubtitle(AVPacket* pkt, AVSubtitle* sub); + int GetPacket(AVPacket *pkt); + void Seek(int64_t timestamp); AVFormatContext* fmt_ctx() const diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index aca748119..7980e10a7 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -376,46 +376,15 @@ QString GetAssTime(const rational &time) bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) { - AVSubtitle subtitle; - memset(&subtitle, 0, sizeof(subtitle)); - - AVSubtitleRect rect; - memset(&rect, 0, sizeof(rect)); - - QString ass_line = QStringLiteral("Dialogue: 0,%1,%2,Default,,0,0,0,,%3").arg( - GetAssTime(sub_block->in()), - GetAssTime(sub_block->out()), - sub_block->GetText() - ); - QByteArray utf8_sub = sub_block->GetText().toUtf8(); - QByteArray utf8_ass = ass_line.toUtf8(); - - rect.type = SUBTITLE_ASS; - rect.text = utf8_sub.data(); - rect.ass = utf8_ass.data(); - - AVSubtitleRect *rect_array = ▭ - subtitle.num_rects = 1; - subtitle.rects = &rect_array; - - subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor); - subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000); - - QVector out_buf(1024 * 1024); - - int sub_sz = avcodec_encode_subtitle(subtitle_codec_ctx_, out_buf.data(), out_buf.size(), &subtitle); - if (sub_sz < 0) { - return false; - } AVPacket *pkt = av_packet_alloc(); pkt->stream_index = subtitle_stream_->index; - pkt->data = out_buf.data(); - pkt->size = sub_sz; - pkt->pts = subtitle.pts; - pkt->duration = av_rescale_q(subtitle.end_display_time, {1, 1000}, subtitle_codec_ctx_->time_base); + pkt->data = (uint8_t *) utf8_sub.data(); + pkt->size = utf8_sub.size(); + pkt->pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor); + pkt->duration = av_rescale_q(qRound64(sub_block->length().toDouble() * 1000), {1, 1000}, subtitle_codec_ctx_->time_base); pkt->dts = pkt->pts; av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, subtitle_stream_->time_base); From 885366518788944505811e362d440ce833e69ecf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 14 May 2022 15:02:32 -0700 Subject: [PATCH 47/62] config: save on preferences dialog accept --- app/core.cpp | 2 +- app/dialog/configbase/configdialogbase.cpp | 8 +++----- app/dialog/configbase/configdialogbase.h | 2 ++ app/dialog/preferences/preferences.cpp | 12 +++++++++--- app/dialog/preferences/preferences.h | 17 +++++++---------- .../preferences/tabs/preferenceskeyboardtab.cpp | 9 +++++++-- .../preferences/tabs/preferenceskeyboardtab.h | 7 ++++++- 7 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 1b85ae09a..5f00a7b69 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -368,7 +368,7 @@ void Core::DialogImportShow() void Core::DialogPreferencesShow() { - PreferencesDialog pd(main_window_, main_window_->menuBar()); + PreferencesDialog pd(main_window_); pd.exec(); } diff --git a/app/dialog/configbase/configdialogbase.cpp b/app/dialog/configbase/configdialogbase.cpp index 2bd7db4e0..13bbafbb5 100644 --- a/app/dialog/configbase/configdialogbase.cpp +++ b/app/dialog/configbase/configdialogbase.cpp @@ -73,11 +73,9 @@ void ConfigDialogBase::accept() tab->Accept(command); } - if (command->child_count() == 0) { - delete command; - } else { - Core::instance()->undo_stack()->push(command); - } + Core::instance()->undo_stack()->pushIfHasChildren(command); + + AcceptEvent(); QDialog::accept(); } diff --git a/app/dialog/configbase/configdialogbase.h b/app/dialog/configbase/configdialogbase.h index 57266562e..35592175e 100644 --- a/app/dialog/configbase/configdialogbase.h +++ b/app/dialog/configbase/configdialogbase.h @@ -44,6 +44,8 @@ private slots: protected: void AddTab(ConfigDialogBaseTab* tab, const QString& title); + virtual void AcceptEvent(){} + private: QListWidget* list_widget_; diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index 6b2c9d04a..5777e929d 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -32,11 +32,12 @@ #include "tabs/preferencesdisktab.h" #include "tabs/preferencesaudiotab.h" #include "tabs/preferenceskeyboardtab.h" +#include "window/mainwindow/mainwindow.h" namespace olive { -PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) : - ConfigDialogBase(parent) +PreferencesDialog::PreferencesDialog(MainWindow *main_window) : + ConfigDialogBase(main_window) { setWindowTitle(tr("Preferences")); @@ -45,7 +46,12 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) : AddTab(new PreferencesBehaviorTab(), tr("Behavior")); AddTab(new PreferencesDiskTab(), tr("Disk")); AddTab(new PreferencesAudioTab(), tr("Audio")); - AddTab(new PreferencesKeyboardTab(main_menu_bar), tr("Keyboard")); + AddTab(new PreferencesKeyboardTab(main_window), tr("Keyboard")); +} + +void PreferencesDialog::AcceptEvent() +{ + Config::Save(); } } diff --git a/app/dialog/preferences/preferences.h b/app/dialog/preferences/preferences.h index c6ec2b5a6..87cdb7266 100644 --- a/app/dialog/preferences/preferences.h +++ b/app/dialog/preferences/preferences.h @@ -32,25 +32,22 @@ namespace olive { +class MainWindow; + /** * @brief The PreferencesDialog class * - * A dialog for the global application settings. Mostly an interface for Config. Can be loaded from any part of the - * application. + * A dialog for the global application settings. Mostly an interface for Config. */ class PreferencesDialog : public ConfigDialogBase { Q_OBJECT public: - /** - * @brief PreferencesDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow. - */ - PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar); + PreferencesDialog(MainWindow *main_window); + +protected: + virtual void AcceptEvent() override; }; diff --git a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp index d1eaa7823..37eab5abc 100644 --- a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp +++ b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp @@ -27,9 +27,12 @@ #include #include +#include "window/mainwindow/mainwindow.h" + namespace olive { -PreferencesKeyboardTab::PreferencesKeyboardTab(QMenuBar *menubar) +PreferencesKeyboardTab::PreferencesKeyboardTab(MainWindow *main_window) : + main_window_(main_window) { QVBoxLayout* shortcut_layout = new QVBoxLayout(this); @@ -67,7 +70,7 @@ PreferencesKeyboardTab::PreferencesKeyboardTab(QMenuBar *menubar) shortcut_layout->addLayout(reset_shortcut_layout); - setup_kbd_shortcuts(menubar); + setup_kbd_shortcuts(main_window_->menuBar()); } void PreferencesKeyboardTab::Accept(MultiUndoCommand *command) @@ -78,6 +81,8 @@ void PreferencesKeyboardTab::Accept(MultiUndoCommand *command) for (int i=0;iset_action_shortcut(); } + + main_window_->SaveLayout(); } void PreferencesKeyboardTab::setup_kbd_shortcuts(QMenuBar* menubar) { diff --git a/app/dialog/preferences/tabs/preferenceskeyboardtab.h b/app/dialog/preferences/tabs/preferenceskeyboardtab.h index 9df2bdfb9..f66e61f57 100644 --- a/app/dialog/preferences/tabs/preferenceskeyboardtab.h +++ b/app/dialog/preferences/tabs/preferenceskeyboardtab.h @@ -29,11 +29,13 @@ namespace olive { +class MainWindow; + class PreferencesKeyboardTab : public ConfigDialogBaseTab { Q_OBJECT public: - PreferencesKeyboardTab(QMenuBar* menubar); + PreferencesKeyboardTab(MainWindow* main_window); virtual void Accept(MultiUndoCommand* command) override; @@ -132,6 +134,9 @@ private: * key_shortcut_actions and key_shortcut_fields) */ QVector key_shortcut_fields_; + + MainWindow *main_window_; + }; } From 74933951032aa281f49df17a6d80137e0b5ec7f5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 11:06:23 -0700 Subject: [PATCH 48/62] implemented drop shadow node --- app/node/factory.cpp | 15 ++- app/node/factory.h | 1 + app/node/filter/CMakeLists.txt | 1 + app/node/filter/blur/blur.cpp | 16 +-- app/node/filter/dropshadow/CMakeLists.txt | 22 ++++ .../filter/dropshadow/dropshadowfilter.cpp | 98 ++++++++++++++++ app/node/filter/dropshadow/dropshadowfilter.h | 58 +++++++++ app/render/opengl/openglrenderer.cpp | 14 +-- app/shaders/dropshadow.frag | 110 ++++++++++++++++++ 9 files changed, 312 insertions(+), 23 deletions(-) create mode 100644 app/node/filter/dropshadow/CMakeLists.txt create mode 100644 app/node/filter/dropshadow/dropshadowfilter.cpp create mode 100644 app/node/filter/dropshadow/dropshadowfilter.h create mode 100644 app/shaders/dropshadow.frag diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 48128589f..dd82a0805 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -37,6 +37,10 @@ #include "distort/mask/mask.h" #include "distort/transform/transformdistortnode.h" #include "effect/opacity/opacityeffect.h" +#include "filter/blur/blur.h" +#include "filter/dropshadow/dropshadowfilter.h" +#include "filter/mosaic/mosaicfilternode.h" +#include "filter/stroke/stroke.h" #include "generator/matrix/matrix.h" #include "generator/noise/noise.h" #include "generator/polygon/polygon.h" @@ -45,17 +49,14 @@ #include "generator/text/textv1.h" #include "generator/text/textv2.h" #include "generator/text/textv3.h" -#include "filter/blur/blur.h" -#include "filter/mosaic/mosaicfilternode.h" -#include "filter/stroke/stroke.h" #include "input/time/timeinput.h" #include "input/value/valuenode.h" +#include "keying/chromakey/chromakey.h" +#include "keying/colordifferencekey/colordifferencekey.h" +#include "keying/despill/despill.h" #include "math/math/math.h" #include "math/merge/merge.h" #include "math/trigonometry/trigonometry.h" -#include "keying/colordifferencekey/colordifferencekey.h" -#include "keying/despill/despill.h" -#include "keying/chromakey/chromakey.h" #include "output/track/track.h" #include "output/viewer/viewer.h" #include "project/folder/folder.h" @@ -288,6 +289,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new ChromaKeyNode(); case kMaskDistort: return new MaskDistortNode(); + case kDropShadowFilter: + return new DropShadowFilter(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 21610a6f1..d7ca955ff 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -74,6 +74,7 @@ public: kOCIOGradingTransformLinear, kChromaKey, kMaskDistort, + kDropShadowFilter, // Count value kInternalNodeCount diff --git a/app/node/filter/CMakeLists.txt b/app/node/filter/CMakeLists.txt index 08b38908e..ea93d9320 100644 --- a/app/node/filter/CMakeLists.txt +++ b/app/node/filter/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see . add_subdirectory(blur) +add_subdirectory(dropshadow) add_subdirectory(mosaic) add_subdirectory(stroke) diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index e37b4d72c..25a1984c2 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -119,15 +119,15 @@ ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - - Method method = static_cast(job.Get(kMethodInput).toInt()); - // If there's no texture, no need to run an operation - if (job.Get(kTextureInput).toTexture()) { + if (value[kTextureInput].toTexture()) { + + ShaderJob job; + + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + Method method = static_cast(job.Get(kMethodInput).toInt()); bool can_push_job = true; diff --git a/app/node/filter/dropshadow/CMakeLists.txt b/app/node/filter/dropshadow/CMakeLists.txt new file mode 100644 index 000000000..e86f4b95f --- /dev/null +++ b/app/node/filter/dropshadow/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/filter/dropshadow/dropshadowfilter.h + node/filter/dropshadow/dropshadowfilter.cpp + PARENT_SCOPE +) diff --git a/app/node/filter/dropshadow/dropshadowfilter.cpp b/app/node/filter/dropshadow/dropshadowfilter.cpp new file mode 100644 index 000000000..a20b20f23 --- /dev/null +++ b/app/node/filter/dropshadow/dropshadowfilter.cpp @@ -0,0 +1,98 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "dropshadowfilter.h" + +#include "widget/slider/floatslider.h" + +namespace olive { + +#define super Node + +const QString DropShadowFilter::kTextureInput = QStringLiteral("tex_in"); +const QString DropShadowFilter::kColorInput = QStringLiteral("color_in"); +const QString DropShadowFilter::kDistanceInput = QStringLiteral("distance_in"); +const QString DropShadowFilter::kAngleInput = QStringLiteral("angle_in"); +const QString DropShadowFilter::kSoftnessInput = QStringLiteral("radius_in"); +const QString DropShadowFilter::kOpacityInput = QStringLiteral("opacity_in"); +const QString DropShadowFilter::kFastInput = QStringLiteral("fast_in"); + +DropShadowFilter::DropShadowFilter() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0, 0.0, 0.0))); + + AddInput(kDistanceInput, NodeValue::kFloat, 10.0); + + AddInput(kAngleInput, NodeValue::kFloat, 135.0); + + AddInput(kSoftnessInput, NodeValue::kFloat, 10.0); + SetInputProperty(kSoftnessInput, QStringLiteral("min"), 0.0); + + AddInput(kOpacityInput, NodeValue::kFloat, 1.0); + SetInputProperty(kOpacityInput, QStringLiteral("min"), 0.0); + SetInputProperty(kOpacityInput, QStringLiteral("view"), FloatSlider::kPercentage); + + AddInput(kFastInput, NodeValue::kBoolean, false); + + SetEffectInput(kTextureInput); + SetFlags(kVideoEffect); +} + +void DropShadowFilter::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Texture")); + SetInputName(kColorInput, tr("Color")); + SetInputName(kDistanceInput, tr("Distance")); + SetInputName(kAngleInput, tr("Angle")); + SetInputName(kSoftnessInput, tr("Softness")); + SetInputName(kOpacityInput, tr("Opacity")); + SetInputName(kFastInput, tr("Faster (Lower Quality)")); +} + +ShaderCode DropShadowFilter::GetShaderCode(const ShaderRequest &request) const +{ + Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/dropshadow.frag")); +} + +void DropShadowFilter::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (value[kTextureInput].toTexture()) { + ShaderJob job; + + QString iterative = QStringLiteral("previous_iteration_in"); + + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(iterative, value[kTextureInput]); + + if (!qIsNull(value[kSoftnessInput].toDouble())) { + job.SetIterations(3, iterative); + } + + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } +} + +} diff --git a/app/node/filter/dropshadow/dropshadowfilter.h b/app/node/filter/dropshadow/dropshadowfilter.h new file mode 100644 index 000000000..5e820a280 --- /dev/null +++ b/app/node/filter/dropshadow/dropshadowfilter.h @@ -0,0 +1,58 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef DROPSHADOWFILTER_H +#define DROPSHADOWFILTER_H + +#include "node/node.h" + +namespace olive { + +class DropShadowFilter : public Node +{ + Q_OBJECT +public: + DropShadowFilter(); + + NODE_DEFAULT_FUNCTIONS(DropShadowFilter) + + virtual QString Name() const override { return tr("Drop Shadow"); } + virtual QString id() const override { return QStringLiteral("org.olivevideoeditor.Olive.dropshadow"); } + virtual QVector Category() const override { return {kCategoryFilter}; } + virtual QString Description() const override { return tr("Adds a drop shadow to an image."); } + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTextureInput; + static const QString kColorInput; + static const QString kDistanceInput; + static const QString kAngleInput; + static const QString kSoftnessInput; + static const QString kOpacityInput; + static const QString kFastInput; + +}; + +} + +#endif // DROPSHADOWFILTER_H diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 99e80c104..ade819167 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -422,8 +422,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video GL_PREAMBLE; // If this node is iterative, we'll pick up which input here - QString iterative_name; - GLuint iterative_input = 0; + QMap texture_index_map; QVector textures_to_bind; GLuint shader = s.value(); @@ -495,11 +494,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // Set value to bound texture functions_->glUniform1i(variable_location, textures_to_bind.size()); - // If this texture binding is the iterative input, set it here - if (it.key() == job.GetIterativeInput()) { - iterative_input = textures_to_bind.size(); - iterative_name = it.key(); - } + texture_index_map.insert(it.key(), textures_to_bind.size()); textures_to_bind.append({texture, job.GetInterpolation(it.key())}); @@ -655,11 +650,12 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video if (iteration > 0) { // If this is not the first iteration, replace the iterative texture with the one we // last drew - functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); + const QString &iterative_input = job.GetIterativeInput(); + functions_->glActiveTexture(GL_TEXTURE0 + texture_index_map.value(iterative_input)); functions_->glBindTexture(GL_TEXTURE_2D, input_tex->id().value()); // At this time, we only support iterating 2D textures - PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_name)); + PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_input)); } // Swap so that the next iteration, the texture we draw now will be the input texture next diff --git a/app/shaders/dropshadow.frag b/app/shaders/dropshadow.frag new file mode 100644 index 000000000..dbfba07cb --- /dev/null +++ b/app/shaders/dropshadow.frag @@ -0,0 +1,110 @@ +uniform sampler2D tex_in; +uniform vec4 color_in; +uniform float distance_in; +uniform float angle_in; +uniform float radius_in; +uniform float opacity_in; +uniform vec2 resolution_in; +uniform sampler2D previous_iteration_in; +uniform bool fast_in; + +uniform int ove_iteration; + +in vec2 ove_texcoord; +out vec4 frag_color; + +// Gaussian function uses PI +#define M_PI 3.1415926535897932384626433832795 + +// Single gaussian formula (unused, mainly here for documentation/just in case) +//float gaussian(float x, float sigma) { +// return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0)); +//} + +// Double gaussian formula, actually used in the code below +// Should be faster than the single gaussian above since it doesn't need sqrt() +float gaussian2(float x, float y, float sigma) { + return (1.0/((sigma*sigma)*2.0*M_PI))*exp(-0.5*(((x*x) + (y*y))/(sigma*sigma))); +} + +void main(void) { + if (ove_iteration == 2 || radius_in == 0.0) { + + // Merge step + vec4 composite = texture(tex_in, ove_texcoord); + if (composite.a < 1.0) { + // Convert degrees to radians + float shadow_angle = ((angle_in + 90.0)*M_PI)/180.0; + + vec2 shadow_offset = vec2(cos(shadow_angle) * distance_in, sin(shadow_angle) * distance_in); + shadow_offset /= resolution_in; + shadow_offset += ove_texcoord; + + vec4 shadow_color = texture(previous_iteration_in, shadow_offset); + + shadow_color.rgb = color_in.rgb * shadow_color.a; + shadow_color *= 1.0 - composite.a; + shadow_color *= opacity_in; + + composite += shadow_color; + } + frag_color = composite; + + } else { + // We only sample on hard pixels, so we don't accept decimal radii + float real_radius = ceil(radius_in); + + vec4 composite = vec4(0.0); + + float divider, sigma; + + if (fast_in) { + + // Calculate the weight of each pixel based on the radius + divider = 1.0 / real_radius; + + } else { + + // Using (radius = 3 * sigma) because 3 standard deviations covers 97% of the blur according to this document: + // http://chemaguerra.com/gaussian-filter-radius/ + sigma = real_radius; + real_radius *= 3.0; + + // Use gaussian formula to calculate the weight of all pixels + divider = 0.0; + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + divider += gaussian2(i, 0.0, sigma); + } + + } + + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + float weight; + + if (fast_in) { + weight = divider; + } else { + weight = gaussian2(i, 0.0, sigma) / divider; + } + + vec2 pixel_coord = ove_texcoord; + vec4 tex_col; + if (ove_iteration == 0) { + pixel_coord.x += i / resolution_in.x; + + // Pull from main texture + tex_col = texture(tex_in, pixel_coord); + } else if (ove_iteration == 1) { + pixel_coord.y += i / resolution_in.y; + + // Pull from previous iteration + tex_col = texture(previous_iteration_in, pixel_coord); + } + + composite += tex_col * weight; + } + + frag_color = composite; + } + +} From 1fa51613f9f643c223dd45ed00a8f7f364cd9479 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 11:06:45 -0700 Subject: [PATCH 49/62] improved transition behavior This is still not complete, but definitely should help a lot --- app/widget/timelinewidget/tool/pointer.cpp | 127 ++++++++++++++---- app/widget/timelinewidget/tool/pointer.h | 4 + .../view/timelineviewghostitem.h | 1 - 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 9361d779e..7158cb906 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -264,10 +264,30 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, return; } - // Determine if this move is a slide, which is determined by either - bool clips_are_sliding = (slide_instead_of_moving || dynamic_cast(clicked_item)); + bool sliding_due_to_transition = false; - if (clips_are_sliding) { + if (!slide_instead_of_moving) { + // If the user tries to move a transition without moving the clip it belongs to, we turn + // this into a slide + foreach (Block* block, clips) { + if (TransitionBlock* transit = dynamic_cast(block)) { + if (!CanTransitionMove(transit, clips)) { + slide_instead_of_moving = true; + break; + } + } else if (ClipBlock *clip = dynamic_cast(block)) { + if ((clip->in_transition() && !CanTransitionMove(clip->in_transition(), clips)) + || (clip->out_transition() && !CanTransitionMove(clip->out_transition(), clips))) { + slide_instead_of_moving = true; + break; + } + } + } + + sliding_due_to_transition = slide_instead_of_moving; + } + + if (slide_instead_of_moving) { // This is a slide. What we do here is move clips within their own track, between the clips // that they're already next to. We don't allow changing tracks or changing the order of // blocks. @@ -297,17 +317,51 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, Block* latest = latest_block_on_track.value(i.key()); // First we add the block that's out trimming, the one prior to the earliest - TimelineViewGhostItem* earliest_ghost; - if (earliest->previous()) { - earliest_ghost = AddGhostFromBlock(earliest->previous(), Timeline::kTrimOut); - } else { - earliest_ghost = AddGhostFromNull(earliest->in(), earliest->in(), track->ToReference(), Timeline::kTrimOut); + { + TimelineViewGhostItem* earliest_ghost; + bool slide_with_earliest_previous = true; + if (sliding_due_to_transition && earliest->previous()) { + if (TransitionBlock *transit = dynamic_cast(earliest)) { + if (earliest->previous() != transit->connected_out_block()) { + slide_with_earliest_previous = false; + } + } else if (ClipBlock *clip = dynamic_cast(earliest)) { + if (earliest->previous() != clip->in_transition()) { + slide_with_earliest_previous = false; + } + } + } + + if (earliest->previous() && slide_with_earliest_previous) { + earliest_ghost = AddGhostFromBlock(earliest->previous(), Timeline::kTrimOut); + } else { + earliest_ghost = AddGhostFromNull(earliest->in(), earliest->in(), track->ToReference(), Timeline::kTrimOut); + } + SetGhostToSlideMode(earliest_ghost); } - SetGhostToSlideMode(earliest_ghost); // Then we add the block that's in trimming, the one after the latest if (latest->next()) { - TimelineViewGhostItem* latest_ghost = AddGhostFromBlock(latest->next(), Timeline::kTrimIn); + TimelineViewGhostItem* latest_ghost; + + bool slide_with_latest_next = true; + if (sliding_due_to_transition) { + if (TransitionBlock *transit = dynamic_cast(latest)) { + if (latest->next() != transit->connected_in_block()) { + slide_with_latest_next = false; + } + } else if (ClipBlock *clip = dynamic_cast(latest)) { + if (latest->next() != clip->out_transition()) { + slide_with_latest_next = false; + } + } + } + + if (slide_with_latest_next) { + latest_ghost = AddGhostFromBlock(latest->next(), Timeline::kTrimIn); + } else { + latest_ghost = AddGhostFromNull(latest->out(), latest->out(), track->ToReference(), Timeline::kTrimIn); + } SetGhostToSlideMode(latest_ghost); } @@ -329,13 +383,18 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, } else { // Prepare for a standard pointer move by creating ghosts for them and any related blocks foreach (Block* block, clips) { - if (dynamic_cast(block) || dynamic_cast(block)) { - // Gaps cannot move, and we handle transitions further down - continue; - } - // Create ghost for this block - AddGhostFromBlock(block, trim_mode, true); + auto ghost = AddGhostFromBlock(block, trim_mode, true); + Q_UNUSED(ghost) + + if (ClipBlock *clip = dynamic_cast(block)) { + if (clip->out_transition()) { + AddGhostFromBlock(clip->out_transition(), trim_mode, true); + } + if (clip->in_transition()) { + AddGhostFromBlock(clip->in_transition(), trim_mode, true); + } + } } } @@ -454,6 +513,18 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, } } +bool PointerTool::CanTransitionMove(TransitionBlock *transit, const QVector &clips) +{ + Block *out = transit->connected_out_block(); + Block *in = transit->connected_in_block(); + + if ((out && !clips.contains(out)) || (in && !clips.contains(in))) { + return false; + } + + return true; +} + void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) { // Calculate track movement @@ -691,8 +762,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) } if (!movement.isNull()) { - QHash >::const_iterator i; - for (i=slide_info.constBegin(); i!=slide_info.constEnd(); i++) { + for (auto i=slide_info.constBegin(); i!=slide_info.constEnd(); i++) { command->add_child(new TrackSlideCommand(parent()->GetTrackFromReference(i.key()), i.value(), in_adjacents.value(i.key()), @@ -737,6 +807,17 @@ void PointerTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_ InitiateDragInternal(clicked_item, trim_mode, modifiers, false, false, false); } +TimelineViewGhostItem *PointerTool::GetExistingGhostFromBlock(Block *block) +{ + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + if (Node::ValueToPtr(ghost->GetData(TimelineViewGhostItem::kAttachedBlock)) == block) { + return ghost; + } + } + + return nullptr; +} + //#define HIDE_GAP_GHOSTS TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, Timeline::MovementMode mode, bool check_if_exists) @@ -747,17 +828,17 @@ TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, Timeline::Mo return nullptr; } + TimelineViewGhostItem* ghost; + // Check if we've already made a ghost for this block if (check_if_exists) { - foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { - if (Node::ValueToPtr(ghost->GetData(TimelineViewGhostItem::kAttachedBlock)) == block) { - return ghost; - } + if ((ghost = GetExistingGhostFromBlock(block))) { + return ghost; } } // Otherwise, it's time to make a ghost for this block - TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block); + ghost = TimelineViewGhostItem::FromBlock(block); #ifdef HIDE_GAP_GHOSTS if (block->type() == Block::kGap) { diff --git a/app/widget/timelinewidget/tool/pointer.h b/app/widget/timelinewidget/tool/pointer.h index abea934e1..be4e0cf90 100644 --- a/app/widget/timelinewidget/tool/pointer.h +++ b/app/widget/timelinewidget/tool/pointer.h @@ -41,6 +41,8 @@ protected: virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers); + TimelineViewGhostItem *GetExistingGhostFromBlock(Block *block); + TimelineViewGhostItem* AddGhostFromBlock(Block *block, Timeline::MovementMode mode, bool check_if_exists = false); TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const Track::Reference& track, Timeline::MovementMode mode); @@ -72,6 +74,8 @@ protected: const Timeline::MovementMode& drag_movement_mode() const { return drag_movement_mode_; } void set_drag_movement_mode(const Timeline::MovementMode &d) { drag_movement_mode_ = d; } + static bool CanTransitionMove(TransitionBlock *transit, const QVector &clips); + void SetMovementAllowed(bool e) { movement_allowed_ = e; diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.h b/app/widget/timelinewidget/view/timelineviewghostitem.h index bbabf1341..16a3c0e8a 100644 --- a/app/widget/timelinewidget/view/timelineviewghostitem.h +++ b/app/widget/timelinewidget/view/timelineviewghostitem.h @@ -75,7 +75,6 @@ public: ghost->can_have_zero_length_ = false; } else if (dynamic_cast(block)) { ghost->can_have_zero_length_ = false; - ghost->SetCanMoveTracks(false); } return ghost; From a2ff3d607aa8a7659dd2b3f02bcdd573fbc29f58 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 11:27:28 -0700 Subject: [PATCH 50/62] serializer: skip overwriting subtitle params Fixes #1934 --- app/node/project/serializer/serializer220403.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 5fb2ab4ab..5a0a39822 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -700,6 +700,14 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node NodeValue::Type data_type = node->GetInputDataType(input); + // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite + // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see + // if this ends up being an issue in the future. + if (data_type == NodeValue::kSubtitleParams) { + reader->skipCurrentElement(); + return; + } + while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("standard")) { // Load standard value From e9844ee135ece1e2a1d27a100bb05b5062b64b7f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 14:36:30 -0700 Subject: [PATCH 51/62] implemented gizmo transforming through the node graph --- app/node/distort/crop/cropdistortnode.cpp | 6 +- .../transform/transformdistortnode.cpp | 21 ++++-- .../distort/transform/transformdistortnode.h | 1 + app/node/generator/matrix/matrix.cpp | 50 +++----------- app/node/generator/matrix/matrix.h | 2 +- app/node/hashtraverser.cpp | 2 +- app/node/node.h | 2 + app/node/output/viewer/viewer.cpp | 4 +- app/node/traverser.cpp | 65 +++++++++++++++++-- app/node/traverser.h | 10 ++- app/render/job/shaderjob.h | 6 ++ app/render/renderprocessor.cpp | 6 +- app/widget/nodevaluetree/nodevaluetree.cpp | 3 +- app/widget/viewer/viewerdisplay.cpp | 51 +++++++++++---- app/widget/viewer/viewerdisplay.h | 7 +- 15 files changed, 157 insertions(+), 79 deletions(-) diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index a55993ff8..5b52df6b1 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -79,10 +79,12 @@ void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global { ShaderJob job; job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); + job.SetWillChangeImageSize(false); + + if (TexturePtr texture = job.Get(kTextureInput).toTexture()) { + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture->params().width(), texture->params().height()), this)); - if (job.Get(kTextureInput).toTexture()) { if (!qIsNull(job.Get(kLeftInput).toDouble()) || !qIsNull(job.Get(kRightInput).toDouble()) || !qIsNull(job.Get(kTopInput).toDouble()) diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index d8cc8ecd9..3743698c1 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -84,7 +84,7 @@ void TransformDistortNode::Retranslate() void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Generate matrix - QMatrix4x4 generated_matrix = GenerateMatrix(value, true, false, false, false); + QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false); // Pop texture NodeValue texture_meta = value[kTextureInput]; @@ -143,7 +143,7 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo TexturePtr tex = db[kTextureInput].toTexture(); if (tex) { VideoParams tex_params = tex->params(); - QMatrix4x4 matrix = GenerateMatrix(db, true, false, false, false); + QMatrix4x4 matrix = GenerateMatrix(db, false, false, false); matrix = GenerateAutoScaledMatrix(matrix, db, globals, tex_params); if (!matrix.isIdentity()) { @@ -162,7 +162,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou if (gizmo == anchor_gizmo_) { - gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, false).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false).toTransform().inverted(); } else if (IsAScaleGizmo(gizmo)) { @@ -204,7 +204,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } // Store current matrix - gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, true).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true).toTransform().inverted(); } else if (gizmo == rotation_gizmo_) { @@ -389,7 +389,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Fold values into a matrix for the rectangle QMatrix4x4 rectangle_matrix; rectangle_matrix.scale(sequence_half_res); - rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, false), + rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false), sequence_res, tex_sz, tex_offset, @@ -409,7 +409,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Draw anchor point QMatrix4x4 anchor_matrix; anchor_matrix.scale(sequence_half_res); - anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, true, false, false), + anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false), sequence_res, tex_sz, tex_offset, @@ -432,6 +432,15 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N SetInputProperty(kAnchorInput, QStringLiteral("offset"), tex_sz * 0.5); } +QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const +{ + if (TexturePtr texture = row[kTextureInput].toTexture()) { + auto m = GenerateMatrix(row, false, false, false); + return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform(); + } + return super::GizmoTransformation(row, globals); +} + QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat) { return mat.map(QPointF(x, y)) + half_res; diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index 7d1aa1b3b..b8bbf6c73 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -82,6 +82,7 @@ public: AutoScaleType autoscale_type = kAutoScaleNone); virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override; static const QString kTextureInput; static const QString kAutoscaleInput; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 7dcaf49c0..81e43c4ea 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -90,63 +90,33 @@ void MatrixGenerator::Retranslate() void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Push matrix output - QMatrix4x4 mat = GenerateMatrix(value, true, false, false, false); + QMatrix4x4 mat = GenerateMatrix(value, false, false, false); table->Push(NodeValue::kMatrix, mat, this); } -QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool take, bool ignore_anchor, bool ignore_position, bool ignore_scale) const +QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const { QVector2D anchor; QVector2D position; QVector2D scale; if (!ignore_anchor) { - if (take) { - // Take and store - anchor = value[kAnchorInput].toVec2(); - } else { - // Get and store - anchor = value[kAnchorInput].toVec2(); - } - } else if (take) { - // Just take - value[kAnchorInput].toVec2(); + anchor = value[kAnchorInput].toVec2(); } if (!ignore_scale) { - if (take) { - scale = value[kScaleInput].toVec2(); - } else { - scale = value[kScaleInput].toVec2(); - } - } else if (take) { - value[kScaleInput].toVec2(); + scale = value[kScaleInput].toVec2(); } if (!ignore_position) { - if (take) { - position = value[kPositionInput].toVec2(); - } else { - position = value[kPositionInput].toVec2(); - } - } else if (take) { - value[kPositionInput].toVec2(); + position = value[kPositionInput].toVec2(); } - if (take) { - return GenerateMatrix(position, - value[kRotationInput].toDouble(), - scale, - value[kUniformScaleInput].toBool(), - anchor); - } else { - return GenerateMatrix(position, - value[kRotationInput].toDouble(), - scale, - value[kUniformScaleInput].toBool(), - anchor); - - } + return GenerateMatrix(position, + value[kRotationInput].toDouble(), + scale, + value[kUniformScaleInput].toBool(), + anchor); } QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 62ed196c4..5d93cb51d 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -53,7 +53,7 @@ public: static const QString kAnchorInput; protected: - QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool take, bool ignore_anchor, bool ignore_position, bool ignore_scale) const; + QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const; static QMatrix4x4 GenerateMatrix(const QVector2D &pos, const float &rot, const QVector2D &scale, diff --git a/app/node/hashtraverser.cpp b/app/node/hashtraverser.cpp index ad4e8df60..1422df346 100644 --- a/app/node/hashtraverser.cpp +++ b/app/node/hashtraverser.cpp @@ -50,7 +50,7 @@ QByteArray HashTraverser::GetHash(const Node *node, const Node::ValueHint &hint, //Hash(reference); // Our overrides will generate a hash from this - NodeValueTable table = GenerateTable(node, hint, range); + NodeValueTable table = GenerateTable(node, range); NodeValue final_value = GenerateRowValueElement(hint, NodeValue::kTexture, &table); HashNodeValue(final_value); diff --git a/app/node/node.h b/app/node/node.h index 74ef7a484..47cdeaa1d 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -889,6 +889,8 @@ public: return gizmos_; } + virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { return QTransform(); } + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals){} const QString& GetLabel() const; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 536d6de39..57767b6b1 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -381,7 +381,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const switch (type) { case Track::kVideo: if (IsInputConnected(kTextureInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), GetValueHintForInput(kTextureInput), TimeRange(0, 0)); + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); if (!r.isNaN()) { return r; @@ -390,7 +390,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const break; case Track::kAudio: if (IsInputConnected(kSamplesInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), GetValueHintForInput(kSamplesInput), TimeRange(0, 0)); + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value();; if (!r.isNaN()) { return r; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 9cdce106b..75ff4b3a8 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -141,6 +141,16 @@ int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString return GenerateRowValueElementIndex(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); } +void NodeTraverser::Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range) +{ + transform_ = transform; + transform_start_ = start; + + GenerateTable(end, range); + + transform_ = nullptr; +} + NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const TimeRange &time) { return NodeGlobals(QVector2D(params.width(), params.height()), params.pixel_aspect_ratio(), time); @@ -179,6 +189,20 @@ int NodeTraverser::GetChannelCountFromJob(const GenerateJob &job) return VideoParams::kRGBAChannelCount; } +TexturePtr NodeTraverser::GetMainTextureFromJob(const GenerateJob &job) +{ + // FIXME: Should probably take Node::GetEffectInput into account here + for (auto it=job.GetValues().cbegin(); it!=job.GetValues().cend(); it++) { + if (it.value().type() == NodeValue::kTexture) { + if (TexturePtr t = it.value().toTexture()) { + return t; + } + } + } + + return nullptr; +} + NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { // If input is connected, retrieve value directly @@ -187,7 +211,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); // Value will equal something from the connected node, follow it - return GenerateTable(node->GetConnectedOutput(input), node->GetValueHintForInput(input), adjusted_range); + return GenerateTable(node->GetConnectedOutput(input), adjusted_range); } else { @@ -205,7 +229,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range); if (node->IsInputConnected(input, i)) { - sub_tbl = GenerateTable(node->GetConnectedOutput(input, i), node->GetValueHintForInput(input, i), adjusted_range); + sub_tbl = GenerateTable(node->GetConnectedOutput(input, i), adjusted_range); } else { QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i); sub_tbl.Push(node->GetInputDataType(input), input_value, node); @@ -231,11 +255,12 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu } NodeTraverser::NodeTraverser() : - cancel_(nullptr) + cancel_(nullptr), + transform_(nullptr) { } -NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint &hint, const TimeRange& range) +NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range) { const Track* track = dynamic_cast(n); if (track) { @@ -264,7 +289,24 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint NodeValueTable table = database.Merge(); // By this point, the node should have all the inputs it needs to render correctly - n->Value(row, GenerateGlobals(video_params_, range), &table); + NodeGlobals globals = GenerateGlobals(video_params_, range); + n->Value(row, globals, &table); + + if (transform_) { + if (!transform_start_) { + if (!transform_ignore_.contains(n)) { + QTransform t = n->GizmoTransformation(row, globals); + if (!t.isIdentity()) { + qDebug() << "transforming" << n; + (*transform_) *= t; + } + + transform_ignore_.append(n); + } + } else if (transform_start_ == n) { + transform_start_ = nullptr; + } + } return table; } else { @@ -288,7 +330,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR NodeValueTable table; if (active_block) { - table = GenerateTable(active_block, track->GetValueHintForInput(Track::kBlockInput, track->GetArrayIndexFromBlock(active_block)), Track::TransformRangeForBlock(active_block, range)); + table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range)); } return table; @@ -307,12 +349,21 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) ShaderJob job = val.value(); + PreProcessRow(range, job.GetValues()); + VideoParams tex_params = GetCacheVideoParams(); tex_params.set_channel_count(GetChannelCountFromJob(job)); + if (!job.GetWillChangeImageSize()) { + if (TexturePtr texture = GetMainTextureFromJob(job)) { + tex_params.set_width(texture->params().width()); + tex_params.set_height(texture->params().height()); + tex_params.set_divider(texture->params().divider()); + } + } + TexturePtr tex = CreateTexture(tex_params); - PreProcessRow(range, job.GetValues()); ProcessShader(tex, val.source(), range, job); val.set_value(tex); diff --git a/app/node/traverser.h b/app/node/traverser.h index ac770dc19..9471fec0f 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -37,7 +37,7 @@ class NodeTraverser public: NodeTraverser(); - NodeValueTable GenerateTable(const Node *n, const Node::ValueHint &hint, const TimeRange &range); + NodeValueTable GenerateTable(const Node *n, const TimeRange &range); NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); @@ -50,6 +50,8 @@ public: int GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table); int GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table); + void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range); + static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const TimeRange &time); static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const rational &time) { @@ -78,6 +80,8 @@ public: static int GetChannelCountFromJob(const GenerateJob& job); + static TexturePtr GetMainTextureFromJob(const GenerateJob& job); + protected: NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range); @@ -152,6 +156,10 @@ private: const QAtomicInt *cancel_; + const Node *transform_start_; + QTransform *transform_; + QVector transform_ignore_; + }; } diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h index 4ee789a98..0669e0c42 100644 --- a/app/render/job/shaderjob.h +++ b/app/render/job/shaderjob.h @@ -36,6 +36,7 @@ public: { iterations_ = 1; iterative_input_ = nullptr; + will_change_image_size_ = true; } const QString& GetShaderID() const @@ -99,6 +100,9 @@ public: return vertex_overrides_; } + bool GetWillChangeImageSize() const { return will_change_image_size_; } + void SetWillChangeImageSize(bool e) { will_change_image_size_ = e; } + private: QString shader_id_; @@ -110,6 +114,8 @@ private: QVector vertex_overrides_; + bool will_change_image_size_; + }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 598fe75e9..ab33e7522 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -52,7 +52,7 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational NodeValueTable table; if (Node *texture_output = viewer->GetConnectedTextureOutput()) { - table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kTextureInput), range); + table = GenerateTable(texture_output, range); } NodeValue tex_val = table.Get(NodeValue::kTexture); @@ -193,7 +193,7 @@ void RenderProcessor::Run() NodeValueTable table; if (Node *texture_output = viewer->GetConnectedSampleOutput()) { - table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kSamplesInput),time); + table = GenerateTable(texture_output, time); } NodeValue sample_val = table.Get(NodeValue::kSamples); @@ -290,7 +290,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim int max_dest_sz = audio_params.time_to_samples(range_for_block.length()); // Destination buffer - NodeValueTable table = GenerateTable(b, track->GetValueHintForInput(Track::kBlockInput, track->GetArrayIndexFromBlock(b)),Track::TransformRangeForBlock(b, range_for_block)); + NodeValueTable table = GenerateTable(b, Track::TransformRangeForBlock(b, range_for_block)); SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples(); ClipBlock *clip_cast = dynamic_cast(b); diff --git a/app/widget/nodevaluetree/nodevaluetree.cpp b/app/widget/nodevaluetree/nodevaluetree.cpp index 2f263b125..d980d57a1 100644 --- a/app/widget/nodevaluetree/nodevaluetree.cpp +++ b/app/widget/nodevaluetree/nodevaluetree.cpp @@ -31,9 +31,8 @@ void NodeValueTree::SetNode(const NodeInput &input, const rational &time) NodeTraverser traverser; Node *connected_node = input.GetConnectedOutput(); - Node::ValueHint value_hint = input.node()->GetValueHintForInput(input.input(), input.element()); - NodeValueTable table = traverser.GenerateTable(connected_node, value_hint, TimeRange(time, time)); + NodeValueTable table = traverser.GenerateTable(connected_node, TimeRange(time, time)); int index = traverser.GenerateRowValueElementIndex(input.node(), input.input(), input.element(), &table); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index bc5ab4450..1b15a5b0a 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -45,7 +45,6 @@ #include "node/gizmo/point.h" #include "node/gizmo/polygon.h" #include "node/gizmo/screen.h" -#include "node/traverser.h" #include "viewertexteditor.h" #include "window/mainwindow/mainwindow.h" @@ -221,7 +220,7 @@ QPointF ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(const QPointF &po * Inversion will only fail if the viewer has been scaled by 0 in any direction * which I think should never happen. */ - return pos * GenerateGizmoTransform().inverted(); + return pos * GenerateDisplayTransform().inverted(); } void ViewerDisplayWidget::ResetFPSTimer() @@ -253,7 +252,8 @@ void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) add_band_->show(); } else if (event->button() == Qt::LeftButton && gizmos_ - && (current_gizmo_ = TryGizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos())))) { + && (gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(), + current_gizmo_ = TryGizmoPress(gizmo_db_, event->pos() * gizmo_last_draw_transform_inverted_))) { // Handle gizmo click gizmo_start_drag_ = event->pos(); @@ -300,7 +300,7 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) // Signal movement if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { if (!gizmo_drag_started_) { - QPointF start = TransformViewerSpaceToBufferSpace(gizmo_start_drag_); + QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; rational gizmo_time = GetGizmoTime(); NodeTraverser t; @@ -311,17 +311,17 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) gizmo_drag_started_ = true; } - QPointF v = TransformViewerSpaceToBufferSpace(event->pos()); + QPointF v = event->pos() * gizmo_last_draw_transform_inverted_; switch (draggable->GetDragValueBehavior()) { case DraggableGizmo::kAbsolute: // Above value is correct break; case DraggableGizmo::kDeltaFromPrevious: - v -= TransformViewerSpaceToBufferSpace(gizmo_last_drag_); + v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_; gizmo_last_drag_ = event->pos(); break; case DraggableGizmo::kDeltaFromStart: - v -= TransformViewerSpaceToBufferSpace(gizmo_start_drag_); + v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; break; } @@ -349,7 +349,7 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) const QRect &band_rect = add_band_->geometry(); if (band_rect.width() > 1 && band_rect.height() > 1) { - QRectF r = GenerateGizmoTransform().inverted().mapRect(add_band_->geometry()); + QRectF r = GenerateDisplayTransform().inverted().mapRect(add_band_->geometry()); emit CreateAddableAt(r); } @@ -510,7 +510,8 @@ void ViewerDisplayWidget::OnPaint() gizmo_db_ = gt.GenerateRow(gizmos_, range); QPainter p(inner_widget()); - p.setWorldTransform(GenerateGizmoTransform()); + gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, range); + p.setWorldTransform(gizmo_last_draw_transform_); gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { @@ -722,7 +723,7 @@ QTransform ViewerDisplayWidget::GenerateWorldTransform() return world; } -QTransform ViewerDisplayWidget::GenerateGizmoTransform() +QTransform ViewerDisplayWidget::GenerateDisplayTransform() { QVector2D viewer_scale(GetTexturePosition(size())); QTransform gizmo_transform = GenerateWorldTransform(); @@ -731,13 +732,37 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform() return gizmo_transform; } +QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const TimeRange &range) +{ + QTransform t = GenerateDisplayTransform(); + if (GetTimeTarget()) { + t.translate(gizmo_params_.width()*0.5, gizmo_params_.height()*0.5); + + Node *target = GetTimeTarget(); + if (ViewerOutput *v = dynamic_cast(target)) { + if (Node *n = v->GetConnectedTextureOutput()) { + target = n; + } + } + + QTransform nt; + gt.Transform(&nt, gizmos_, target, range); + + t = nt * t; + + t.translate(-gizmo_params_.width()*0.5, -gizmo_params_.height()*0.5); + } + + return t; +} + NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPointF &p) { for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) { NodeGizmo *gizmo = *it; if (gizmo->IsVisible()) { if (PointGizmo *point = dynamic_cast(gizmo)) { - if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { + if (point->GetClickingRect(GenerateDisplayTransform()).contains(p)) { return point; } } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { @@ -760,7 +785,7 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) { - QTransform gizmo_transform = GenerateGizmoTransform(); + QTransform gizmo_transform = GenerateDisplayTransform(); ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); Html::HtmlToDoc(text_edit->document(), text->GetHtml()); @@ -821,7 +846,7 @@ void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) Color reference, display; if (texture_) { - QPointF pixel_pos = GenerateGizmoTransform().inverted().map(e->pos()); + QPointF pixel_pos = GenerateDisplayTransform().inverted().map(e->pos()); pixel_pos /= texture_->params().divider(); reference = renderer()->GetPixelFromTexture(texture_.get(), pixel_pos); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 54320d33f..cc2a4b0e9 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -29,6 +29,7 @@ #include "node/gizmo/text.h" #include "node/node.h" #include "node/output/track/tracklist.h" +#include "node/traverser.h" #include "render/color.h" #include "tool/tool.h" #include "viewerplaybacktimer.h" @@ -262,7 +263,9 @@ private: QTransform GenerateWorldTransform(); - QTransform GenerateGizmoTransform(); + QTransform GenerateDisplayTransform(); + + QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range); TimeRange GenerateGizmoTime() { @@ -326,6 +329,8 @@ private: QPoint gizmo_last_drag_; NodeGizmo *current_gizmo_; bool gizmo_drag_started_; + QTransform gizmo_last_draw_transform_; + QTransform gizmo_last_draw_transform_inverted_; bool show_subtitles_; Sequence *subtitle_tracks_; From 09c57422f9397a2952a750bbdce495409ecf5d6e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 19:33:01 -0700 Subject: [PATCH 52/62] ffmpeg: significantly optimized decoder --- app/codec/decoder.cpp | 12 +- app/codec/decoder.h | 3 +- app/codec/ffmpeg/CMakeLists.txt | 6 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 178 ++++++++++++--------------- app/codec/ffmpeg/ffmpegdecoder.h | 12 +- app/codec/ffmpeg/ffmpegframepool.cpp | 51 -------- app/codec/ffmpeg/ffmpegframepool.h | 63 ---------- app/common/ffmpegutils.cpp | 4 +- app/node/traverser.cpp | 16 ++- app/render/rendermanager.cpp | 22 ---- app/render/rendermanager.h | 7 -- 11 files changed, 117 insertions(+), 257 deletions(-) delete mode 100644 app/codec/ffmpeg/ffmpegframepool.cpp delete mode 100644 app/codec/ffmpeg/ffmpegframepool.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index c793f55d0..af4b45d34 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -199,9 +199,17 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } -int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time) +int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time, VideoParams::Interlacing interlacing) { - return Timecode::time_to_timestamp(time, timebase) + start_time; + int64_t t = Timecode::time_to_timestamp(time, timebase); + t += start_time; + return t; +} + +rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time, VideoParams::Interlacing interlacing) +{ + time -= start_time; + return Timecode::timestamp_to_time(time, timebase); } void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 35410c0d1..c3b501cc1 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -295,7 +295,8 @@ protected: return stream_; } - static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); + static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing); + static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing); signals: /** diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index 70d978888..0401fba78 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -16,11 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - codec/ffmpeg/ffmpegdecoder.h codec/ffmpeg/ffmpegdecoder.cpp - codec/ffmpeg/ffmpegencoder.h + codec/ffmpeg/ffmpegdecoder.h codec/ffmpeg/ffmpegencoder.cpp - codec/ffmpeg/ffmpegframepool.h - codec/ffmpeg/ffmpegframepool.cpp + codec/ffmpeg/ffmpegencoder.h PARENT_SCOPE ) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 1da0881ef..3abb9a86a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -54,7 +54,8 @@ FFmpegDecoder::FFmpegDecoder() : filter_graph_(nullptr), buffersrc_ctx_(nullptr), buffersink_ctx_(nullptr), - pool_(QThread::idealThreadCount()*2), + working_frame_(nullptr), + working_packet_(nullptr), is_working_(false), cache_at_zero_(false), cache_at_eof_(false) @@ -80,11 +81,14 @@ bool FFmpegDecoder::OpenInternal() if (native_pix_fmt_ == VideoParams::kFormatInvalid || native_channel_count_ == 0) { - qDebug() << "Failed to find valid native pixel format for" << ideal_pix_fmt_; + qCritical() << "Failed to find valid native pixel format for" << ideal_pix_fmt_; return false; } } + working_frame_ = av_frame_alloc(); + working_packet_ = av_packet_alloc(); + return true; } @@ -153,35 +157,21 @@ FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const Re return nullptr; } - AVStream* s = instance_.avstream(); - - // Retrieve frame - FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, cancelled); - - // We found the frame, we'll return a copy - if (return_frame) { - FramePtr copy = Frame::Create(); - copy->set_video_params(VideoParams(s->codecpar->width, - s->codecpar->height, - native_pix_fmt_, - native_channel_count_, - av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect, - VideoParams::kInterlaceNone, - filter_params_.divider)); - copy->set_timestamp(timecode); - copy->allocate(); - - // This data will already match the frame - memcpy(copy->data(), return_frame->data(), copy->allocated_size()); - - return copy; - } - - return nullptr; + return RetrieveFrame(timecode, cancelled); } void FFmpegDecoder::CloseInternal() { + if (working_packet_) { + av_packet_free(&working_packet_); + working_packet_ = nullptr; + } + + if (working_frame_) { + av_frame_free(&working_frame_); + working_frame_ = nullptr; + } + ClearFrameCache(); instance_.Close(); @@ -192,35 +182,35 @@ int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame) // Ensure scaler is correct for these parameters int ret; - AVFrame* working_frame = av_frame_alloc(); - // Try to pull frame from buffersink while ((ret = av_buffersink_get_frame(buffersink_ctx_, output_frame)) == AVERROR(EAGAIN)) { // If no frame is ready in the buffersink, pull from codec - ret = instance_.GetFrame(packet, working_frame); + ret = instance_.GetFrame(packet, output_frame); if (ret >= 0) { // Override this frame's interlacing parameters from user switch (filter_params_.src_interlacing) { case VideoParams::kInterlaceNone: - working_frame->interlaced_frame = 0; + output_frame->interlaced_frame = 0; break; case VideoParams::kInterlacedTopFirst: - working_frame->interlaced_frame = 1; - working_frame->top_field_first = 1; + output_frame->interlaced_frame = 1; + output_frame->top_field_first = 1; break; case VideoParams::kInterlacedBottomFirst: - working_frame->interlaced_frame = 1; - working_frame->top_field_first = 0; + output_frame->interlaced_frame = 1; + output_frame->top_field_first = 0; break; } // If succeeded in pulling from codec, send to buffer source - ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, working_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, output_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + + av_frame_unref(output_frame); if (ret < 0) { // If failed to send to buffer source, return break and error code - qDebug() << "Failed to feed filter graph:" << FFmpegError(ret); + qCritical() << "Failed to feed filter graph:" << FFmpegError(ret); break; } } else { @@ -229,8 +219,6 @@ int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame) } } - av_frame_free(&working_frame); - return ret; } @@ -685,7 +673,7 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) void FFmpegDecoder::ClearFrameCache() { - if (!cached_frames_.isEmpty()) { + if (!cached_frames_.empty()) { cached_frames_.clear(); cache_at_eof_ = false; cache_at_zero_ = false; @@ -696,24 +684,18 @@ void FFmpegDecoder::ClearFrameCache() } } -FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled) +FramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled) { - int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); + int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time, filter_params_.src_interlacing); const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = target_ts; bool still_seeking = false; - if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { - // If we are de-interlacing, the timebase is doubled because we get one frame per field, so we - // double the target timestamp too - target_ts *= 2; - } - if (time != kAnyTimecode) { // If the frame wasn't in the frame cache, see if this frame cache is too old to use - if (cached_frames_.isEmpty() - || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) { + if (cached_frames_.empty() + || (time < cached_frames_.front()->timestamp() || time > cached_frames_.back()->timestamp() + 2)) { ClearFrameCache(); instance_.Seek(seek_ts); @@ -724,7 +706,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c still_seeking = true; } else { // Search cache for frame - FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts); + FramePtr cached_frame = GetFrameFromCache(time); if (cached_frame) { return cached_frame; } @@ -732,11 +714,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c } int ret; - AVPacket* pkt = av_packet_alloc(); - FFmpegFramePool::ElementPtr return_frame = nullptr; - - // Allocate a new frame - AVFrame* working_frame = av_frame_alloc(); + FramePtr return_frame = nullptr; while (true) { // Break out of loop if we've cancelled @@ -745,8 +723,8 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c } // Pull from the decoder - av_frame_unref(working_frame); - ret = GetFilteredFrame(pkt, working_frame); + av_frame_unref(working_frame_); + ret = GetFilteredFrame(working_packet_, working_frame_); // Handle any errors that aren't EOF (EOF is handled later on) if (ret < 0 && ret != AVERROR_EOF) { @@ -757,7 +735,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c if (still_seeking) { // Handle a failure to seek (occurs on some media) // We'll only be here if the frame cache was emptied earlier - if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->best_effort_timestamp > target_ts)) { + if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame_->best_effort_timestamp > target_ts)) { seek_ts = qMax(min_seek, seek_ts - second_ts_); instance_.Seek(seek_ts); @@ -779,10 +757,10 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c // Handle an "expected" EOF by using the last frame of our cache cache_at_eof_ = true; - if (cached_frames_.isEmpty()) { + if (cached_frames_.empty()) { qCritical() << "Unexpected codec EOF - unable to retrieve frame"; } else { - return_frame = cached_frames_.last(); + return_frame = cached_frames_.back(); } break; @@ -790,42 +768,39 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c } else { // Cut down to thread count - 1 before we acquire a new frame - if (cached_frames_.size() == QThread::idealThreadCount()) { + if (cached_frames_.size() == size_t(QThread::idealThreadCount())) { RemoveFirstFrame(); } - FFmpegFramePool::ElementPtr cached = pool_.Get(); - - if (!cached) { - qCritical() << "Frame pool failed to return a valid frame - out of memory?"; - break; - } + FramePtr cached = Frame::Create(); + cached->set_video_params(GetVideoParams()); + cached->allocate(); // Store in queue, converting to native format - uint8_t* destination_data = cached->data(); - int destination_linesize = Frame::generate_linesize_bytes(working_frame->width, native_pix_fmt_, native_channel_count_); + uint8_t* destination_data = reinterpret_cast(cached->data()); + int destination_linesize = cached->linesize_bytes(); - av_image_copy(&destination_data, &destination_linesize, const_cast(working_frame->data), working_frame->linesize, static_cast(working_frame->format), working_frame->width, working_frame->height); + av_image_copy(&destination_data, &destination_linesize, const_cast(working_frame_->data), working_frame_->linesize, static_cast(working_frame_->format), working_frame_->width, working_frame_->height); // Set timestamp so this frame can be identified later - cached->set_timestamp(working_frame->best_effort_timestamp); + cached->set_timestamp(GetTimestampInTimeUnits(working_frame_->best_effort_timestamp, instance_.avstream()->time_base, instance_.avstream()->start_time, filter_params_.src_interlacing)); // Store frame before just in case - FFmpegFramePool::ElementPtr previous; - if (cached_frames_.isEmpty()) { + FramePtr previous; + if (cached_frames_.empty()) { previous = nullptr; } else { - previous = cached_frames_.last(); + previous = cached_frames_.back(); } // Append this frame and signal to other threads that a new frame has arrived - cached_frames_.append(cached); + cached_frames_.push_back(cached); // If this is a valid frame, see if this or the frame before it are the one we need - if (cached->timestamp() == target_ts || time == kAnyTimecode) { + if (cached->timestamp() == time || time == kAnyTimecode) { return_frame = cached; break; - } else if (cached->timestamp() > target_ts) { + } else if (cached->timestamp() > time) { if (!previous && cache_at_zero_) { return_frame = cached; break; @@ -837,8 +812,8 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c } } - av_frame_free(&working_frame); - av_packet_free(&pkt); + av_frame_unref(working_frame_); + av_packet_unref(working_packet_); return return_frame; } @@ -939,16 +914,10 @@ bool FFmpegDecoder::InitScaler(const RetrieveVideoParams& params) // Configure graph if (int ret = avfilter_graph_config(filter_graph_, nullptr) < 0) { - qDebug() << "Failed to configure graph:" << FFmpegError(ret); + qCritical() << "Failed to configure graph:" << FFmpegError(ret); return false; } - // Configure frame pool - if (pool_.width() != dst_width || pool_.height() != dst_height) { - // Set new frame pool parameters - pool_.SetParameters(dst_width, dst_height, native_pix_fmt_, native_channel_count_); - } - return true; } @@ -962,32 +931,32 @@ void FFmpegDecoder::FreeScaler() } } -FFmpegFramePool::ElementPtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const +FramePtr FFmpegDecoder::GetFrameFromCache(const rational &t) const { - if (t < cached_frames_.first()->timestamp()) { + if (t < cached_frames_.front()->timestamp()) { if (cache_at_zero_) { - cached_frames_.first()->access(); - return cached_frames_.first(); + return cached_frames_.front(); } - } else if (t > cached_frames_.last()->timestamp()) { + } else if (t > cached_frames_.back()->timestamp()) { if (cache_at_eof_) { - cached_frames_.last()->access(); - return cached_frames_.last(); + return cached_frames_.back(); } } else { // We already have this frame in the cache, find it - for (int i=0;itimestamp() == t // Test for an exact match - || (i < cached_frames_.size() - 1 && cached_frames_.at(i+1)->timestamp() > t)) { // Or for this frame to be the "closest" + || (next != cached_frames_.cend() && (*next)->timestamp() > t)) { // Or for this frame to be the "closest" - this_frame->access(); return this_frame; } @@ -999,10 +968,21 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) c void FFmpegDecoder::RemoveFirstFrame() { - cached_frames_.removeFirst(); + cached_frames_.pop_front(); cache_at_zero_ = false; } +VideoParams FFmpegDecoder::GetVideoParams() const +{ + return VideoParams(instance_.avstream()->codecpar->width, + instance_.avstream()->codecpar->height, + native_pix_fmt_, + native_channel_count_, + av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), + VideoParams::kInterlaceNone, + filter_params_.divider); +} + FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 3d8c56f97..2f377af5d 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -37,7 +37,6 @@ extern "C" { #include #include "codec/decoder.h" -#include "ffmpegframepool.h" namespace olive { @@ -139,14 +138,16 @@ private: static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing); - FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const; + FramePtr GetFrameFromCache(const rational &t) const; void ClearFrameCache(); - FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled); + FramePtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled); void RemoveFirstFrame(); + VideoParams GetVideoParams() const; + RetrieveVideoParams filter_params_; AVFilterGraph* filter_graph_; AVFilterContext* buffersrc_ctx_; @@ -155,11 +156,12 @@ private: VideoParams::Format native_pix_fmt_; int native_channel_count_; - FFmpegFramePool pool_; + AVFrame *working_frame_; + AVPacket *working_packet_; int64_t second_ts_; - QList cached_frames_; + std::list cached_frames_; bool is_working_; QMutex is_working_mutex_; diff --git a/app/codec/ffmpeg/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp deleted file mode 100644 index 1337b14e7..000000000 --- a/app/codec/ffmpeg/ffmpegframepool.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "ffmpegframepool.h" - -#include "codec/frame.h" - -namespace olive { - -FFmpegFramePool::FFmpegFramePool(int element_count) : - MemoryPool(element_count), - width_(0), - height_(0), - format_(VideoParams::kFormatInvalid), - channel_count_(0) -{ -} - -void FFmpegFramePool::SetParameters(int width, int height, VideoParams::Format format, int channel_count) -{ - Clear(); - - width_ = width; - height_ = height; - format_ = format; - channel_count_ = channel_count; -} - -size_t FFmpegFramePool::GetElementSize() -{ - return Frame::generate_linesize_bytes(width_, format_, channel_count_) * height_; -} - -} diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h deleted file mode 100644 index 8107ec324..000000000 --- a/app/codec/ffmpeg/ffmpegframepool.h +++ /dev/null @@ -1,63 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef FFMPEGFRAMEPOOL_H -#define FFMPEGFRAMEPOOL_H - -#include "common/memorypool.h" -#include "render/videoparams.h" - -namespace olive { - -class FFmpegFramePool : public MemoryPool -{ - Q_OBJECT -public: - FFmpegFramePool(int element_count); - - void SetParameters(int width, int height, VideoParams::Format format, int channel_count); - - const int& width() const - { - return width_; - } - - const int& height() const - { - return height_; - } - -protected: - virtual size_t GetElementSize() override; - -private: - int width_; - - int height_; - - VideoParams::Format format_; - - int channel_count_; - -}; - -} - -#endif // FFMPEGFRAMEPOOL_H diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index d05252bb9..411f0a115 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -25,9 +25,9 @@ namespace olive { AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) { AVPixelFormat possible_pix_fmts[] = { - AV_PIX_FMT_RGB24, + // RGBA formats only because GPUs always upconvert to RGBA, so if it's RGB, that adds extra + // conversion overhead AV_PIX_FMT_RGBA, - AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64, AV_PIX_FMT_NONE }; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 75ff4b3a8..a7738ba20 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -260,8 +260,23 @@ NodeTraverser::NodeTraverser() : { } +class GTTTime +{ +public: + GTTTime(const Node *n) { t = QDateTime::currentMSecsSinceEpoch(); node = n; } + + ~GTTTime() { qDebug() << "GT for" << node << "took" << (QDateTime::currentMSecsSinceEpoch() - t); } + + qint64 t; + const Node *node; + +}; + NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range) { + // NOTE: Times how long a node takes to process, useful for profiling. + //GTTTime gtt(n);Q_UNUSED(gtt); + const Track* track = dynamic_cast(n); if (track) { // If the range is not wholly contained in this Block, we'll need to do some extra processing @@ -297,7 +312,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang if (!transform_ignore_.contains(n)) { QTransform t = n->GizmoTransformation(row, globals); if (!t.isIdentity()) { - qDebug() << "transforming" << n; (*transform_) *= t; } diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index e779820ff..772048134 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -56,10 +56,6 @@ RenderManager::RenderManager(QObject *parent) : decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString())); - - decoder_clear_timer_.setInterval(kDecoderMaximumInactivity); - connect(&decoder_clear_timer_, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); - decoder_clear_timer_.start(); } else { qCritical() << "Tried to initialize unknown graphics backend"; context_ = nullptr; @@ -81,24 +77,6 @@ RenderManager::~RenderManager() } } -void RenderManager::ClearOldDecoders() -{ - QMutexLocker locker(decoder_cache_->mutex()); - - qint64 min_age = QDateTime::currentMSecsSinceEpoch() - kDecoderMaximumInactivity; - - for (auto it=decoder_cache_->begin(); it!=decoder_cache_->end(); ) { - DecoderPair decoder = it.value(); - - if (decoder.decoder->GetLastAccessedTime() < min_age) { - decoder.decoder->Close(); - it = decoder_cache_->erase(it); - } else { - it++; - } - } -} - QByteArray RenderManager::Hash(const Node *n, const Node::ValueHint &output, const VideoParams ¶ms, const rational &time) { Q_ASSERT(n); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 39b52fc9f..c5d4fac12 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -142,13 +142,6 @@ private: QVariant default_shader_; - QTimer decoder_clear_timer_; - - static constexpr auto kDecoderMaximumInactivity = 10000; - -private slots: - void ClearOldDecoders(); - }; } From eaa105abf26fb93aa24528f014d8e5f5ee7a3e68 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 20:41:07 -0700 Subject: [PATCH 53/62] gizmos: correctly map cursor positions after new traversing implementation --- app/node/gizmo/point.cpp | 27 ++++++++++++++++++--------- app/node/gizmo/point.h | 2 +- app/widget/viewer/viewerdisplay.cpp | 10 ++++++---- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/node/gizmo/point.cpp b/app/node/gizmo/point.cpp index 0e10d9258..d60ccf5bd 100644 --- a/app/node/gizmo/point.cpp +++ b/app/node/gizmo/point.cpp @@ -43,7 +43,7 @@ PointGizmo::PointGizmo(QObject *parent) : void PointGizmo::Draw(QPainter *p) const { - QRectF rect = GetDrawingRect(GetStandardRadius() / p->transform().m11()); + QRectF rect = GetDrawingRect(p->transform(), GetStandardRadius()); if (shape_ != kAnchorPoint) { p->setPen(Qt::NoPen); @@ -72,7 +72,7 @@ void PointGizmo::Draw(QPainter *p) const QRectF PointGizmo::GetClickingRect(const QTransform &t) const { - return GetDrawingRect(GetStandardRadius() / t.m11() * 1.5); + return GetDrawingRect(t, GetStandardRadius()); } double PointGizmo::GetStandardRadius() @@ -80,20 +80,29 @@ double PointGizmo::GetStandardRadius() return QFontMetrics(qApp->font()).height() * 0.25; } -QRectF PointGizmo::GetDrawingRect(double radius) const +QRectF PointGizmo::GetDrawingRect(const QTransform &transform, double radius) const { + QRectF r(0,0, radius, radius); + + r = transform.inverted().mapRect(r); + + double width = r.width(); + double height = r.height(); + if (shape_ == kAnchorPoint) { - radius *= 2; + width *= 2; + height *= 2; } if (smaller_) { - radius *= 0.5; + width *= 0.5; + height *= 0.5; } - return QRectF(point_.x() - radius, - point_.y() - radius, - 2*radius, - 2*radius); + return QRectF(point_.x() - width, + point_.y() - height, + 2*width, + 2*height); } } diff --git a/app/node/gizmo/point.h b/app/node/gizmo/point.h index 8204d44d5..8f8a02f65 100644 --- a/app/node/gizmo/point.h +++ b/app/node/gizmo/point.h @@ -57,7 +57,7 @@ public: private: static double GetStandardRadius(); - QRectF GetDrawingRect(double radius) const; + QRectF GetDrawingRect(const QTransform &transform, double radius) const; Shape shape_; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1b15a5b0a..4074cce4a 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -253,7 +253,7 @@ void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) } else if (event->button() == Qt::LeftButton && gizmos_ && (gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(), - current_gizmo_ = TryGizmoPress(gizmo_db_, event->pos() * gizmo_last_draw_transform_inverted_))) { + current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { // Handle gizmo click gizmo_start_drag_ = event->pos(); @@ -736,8 +736,6 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const { QTransform t = GenerateDisplayTransform(); if (GetTimeTarget()) { - t.translate(gizmo_params_.width()*0.5, gizmo_params_.height()*0.5); - Node *target = GetTimeTarget(); if (ViewerOutput *v = dynamic_cast(target)) { if (Node *n = v->GetConnectedTextureOutput()) { @@ -748,8 +746,12 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const QTransform nt; gt.Transform(&nt, gizmos_, target, range); + t.translate(gizmo_params_.width()*0.5, gizmo_params_.height()*0.5); + t.scale(gizmo_params_.width(), gizmo_params_.height()); + t = nt * t; + t.scale(1.0 / gizmo_params_.width(), 1.0 / gizmo_params_.height()); t.translate(-gizmo_params_.width()*0.5, -gizmo_params_.height()*0.5); } @@ -762,7 +764,7 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo NodeGizmo *gizmo = *it; if (gizmo->IsVisible()) { if (PointGizmo *point = dynamic_cast(gizmo)) { - if (point->GetClickingRect(GenerateDisplayTransform()).contains(p)) { + if (point->GetClickingRect(gizmo_last_draw_transform_).contains(p)) { return point; } } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { From 81956d8869f6715a60c8ffdf6edfcc72c979f701 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 21:13:24 -0700 Subject: [PATCH 54/62] viewer: allow queue some time to catch up --- app/widget/viewer/viewer.cpp | 23 ++++++++++++++++++++++- app/widget/viewer/viewer.h | 5 +++++ app/widget/viewer/viewerdisplay.cpp | 11 ++++++++++- app/widget/viewer/viewerdisplay.h | 4 ++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 7a48a4e53..087bb90df 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -95,7 +95,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(display_widget_, &ViewerDisplayWidget::DragEntered, this, &ViewerWidget::DragEntered); connect(display_widget_, &ViewerDisplayWidget::Dropped, this, &ViewerWidget::Dropped); connect(display_widget_, &ViewerDisplayWidget::TextureChanged, this, &ViewerWidget::TextureChanged); - connect(display_widget_, &ViewerDisplayWidget::QueueStarved, this, &ViewerWidget::ForceRequeueFromCurrentTime); + connect(display_widget_, &ViewerDisplayWidget::QueueStarved, this, &ViewerWidget::QueueStarved); + connect(display_widget_, &ViewerDisplayWidget::QueueNoLongerStarved, this, &ViewerWidget::QueueNoLongerStarved); connect(display_widget_, &ViewerDisplayWidget::CreateAddableAt, this, &ViewerWidget::CreateAddableAt); connect(sizer_, &ViewerSizer::RequestScale, display_widget_, &ViewerDisplayWidget::SetMatrixZoom); connect(sizer_, &ViewerSizer::RequestTranslate, display_widget_, &ViewerDisplayWidget::SetMatrixTranslate); @@ -636,6 +637,24 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing() delete watcher; } +void ViewerWidget::QueueStarved() +{ + static const int kMaximumWaitTime = 250; + qint64 now = QDateTime::currentMSecsSinceEpoch(); + + if (!queue_starved_start_) { + queue_starved_start_ = now; + } else if (now > queue_starved_start_ + kMaximumWaitTime) { + ForceRequeueFromCurrentTime(); + queue_starved_start_ = 0; + } +} + +void ViewerWidget::QueueNoLongerStarved() +{ + queue_starved_start_ = 0; +} + void ViewerWidget::ForceRequeueFromCurrentTime() { ClearVideoAutoCacherQueue(); @@ -732,6 +751,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) controls_->ShowPauseButton(); + queue_starved_start_ = 0; + // Attempt to fill playback queue if (display_widget_->isVisible() || !windows_.isEmpty()) { prequeue_length_ = DeterminePlaybackQueueSize(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4e574f179..f99202cb7 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -274,6 +274,8 @@ private: Track::Reference recording_track_; QString recording_filename_; + qint64 queue_starved_start_; + private slots: void PlaybackTimerUpdate(); @@ -321,6 +323,9 @@ private slots: void ReceivedAudioBufferForScrubbing(); + void QueueStarved(); + void QueueNoLongerStarved(); + void ForceRequeueFromCurrentTime(); void UpdateAudioProcessor(); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 4074cce4a..9fc4eeac4 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -67,7 +67,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : frames_skipped_(0), show_widget_background_(false), push_mode_(kPushNull), - add_band_(nullptr) + add_band_(nullptr), + queue_starved_(false) { connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged); @@ -894,6 +895,7 @@ void ViewerDisplayWidget::Pause() disconnect(this, &ViewerDisplayWidget::frameSwapped, this, &ViewerDisplayWidget::UpdateFromQueue); queue_.clear(); + queue_starved_ = false; } void ViewerDisplayWidget::UpdateFromQueue() @@ -905,6 +907,7 @@ void ViewerDisplayWidget::UpdateFromQueue() bool popped = false; if (queue_.empty()) { + queue_starved_ = true; emit QueueStarved(); } else { while (!queue_.empty()) { @@ -914,6 +917,11 @@ void ViewerDisplayWidget::UpdateFromQueue() // Frame was in queue, no need to decode anything SetImage(pf.frame); + + if (queue_starved_) { + queue_starved_ = false; + emit QueueNoLongerStarved(); + } return; } else if (pf.timestamp > time) { @@ -936,6 +944,7 @@ void ViewerDisplayWidget::UpdateFromQueue() } if (queue_.empty()) { + queue_starved_ = true; emit QueueStarved(); break; } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index cc2a4b0e9..adb51e682 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -212,6 +212,8 @@ signals: void QueueStarved(); + void QueueNoLongerStarved(); + void CreateAddableAt(const QRectF &rect); protected: @@ -384,6 +386,8 @@ private: QRubberBand *add_band_; QPoint add_band_start_; + bool queue_starved_; + private slots: void EmitColorAtCursor(QMouseEvent* e); From ea13f249b7d7ab895856e1ebce70190937381daa Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 21:36:40 -0700 Subject: [PATCH 55/62] render: fixed threading regression --- app/render/previewautocacher.cpp | 24 ++++++++++++------------ app/render/previewautocacher.h | 9 +++++---- app/render/rendermanager.cpp | 20 ++++++++++---------- app/render/rendermanager.h | 16 +++++----------- app/widget/viewer/viewer.cpp | 16 ++++++++-------- app/widget/viewer/viewer.h | 4 ++-- 6 files changed, 42 insertions(+), 47 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 36bd6e212..a791a9fee 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -62,7 +62,7 @@ PreviewAutoCacher::~PreviewAutoCacher() SetViewerNode(nullptr); } -RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool prioritize) +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicketPriority priority) { // If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now CancelQueuedSingleFrameRender(); @@ -78,7 +78,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool priori auto sfr = std::make_shared(); sfr->Start(); sfr->setProperty("time", QVariant::fromValue(t)); - sfr->setProperty("prioritize", prioritize); + sfr->setProperty("priority", int(priority)); sfr->setProperty("hash", hash); // Queue it and try to render @@ -88,9 +88,9 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool priori return sfr; } -RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, bool prioritize) +RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicketPriority priority) { - return RenderAudio(range, false, prioritize); + return RenderAudio(range, false, priority); } QVector PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×) @@ -287,7 +287,7 @@ void PreviewAutoCacher::VideoRendered() w->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_node_->video_frame_cache(), frame, hash, - true)); + RenderTicketPriority::kHigh)); } } @@ -637,7 +637,7 @@ void PreviewAutoCacher::TryRender() } else { watcher = RenderFrame(hash, single_frame_render_->property("time").value(), - single_frame_render_->property("prioritize").toBool(), + RenderTicketPriority(single_frame_render_->property("priority").toInt()), !viewer_node_->GetVideoAutoCacheEnabled()); video_immediate_passthroughs_[watcher].append(single_frame_render_); @@ -685,7 +685,7 @@ void PreviewAutoCacher::TryRender() // We want this hash, if we're not already rendering, start render now if (!render_task && !video_download_tasks_.key(hash)) { // Don't render any hash more than once - RenderFrame(hash, t, false, false); + RenderFrame(hash, t, RenderTicketPriority::kNormal, false); } emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); @@ -705,13 +705,13 @@ void PreviewAutoCacher::TryRender() r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); // Start job - RenderAudio(r, true, false); + RenderAudio(r, true, RenderTicketPriority::kNormal); audio_iterator_.remove(r); } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, const rational& time, bool prioritize, bool texture_only) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, const rational& time, RenderTicketPriority priority, bool texture_only) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("hash", hash); @@ -723,19 +723,19 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, cons time, RenderMode::kOffline, viewer_node_->video_frame_cache(), - prioritize, + priority, texture_only)); return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(const TimeRange &r, bool generate_waveforms, bool prioritize) +RenderTicketPtr PreviewAutoCacher::RenderAudio(const TimeRange &r, bool generate_waveforms, RenderTicketPriority priority) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); - RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, generate_waveforms, prioritize); + RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, generate_waveforms, priority); watcher->SetTicket(ticket); return ticket; } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index e923a52e7..0ab8eeb40 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -32,6 +32,7 @@ #include "node/project/project.h" #include "render/audioparams.h" #include "render/renderjobtracker.h" +#include "threading/threadpool.h" #include "threading/threadticketwatcher.h" namespace olive { @@ -49,9 +50,9 @@ public: virtual ~PreviewAutoCacher() override; - RenderTicketPtr GetSingleFrame(const rational& t, bool prioritize); + RenderTicketPtr GetSingleFrame(const rational& t, RenderTicketPriority prioritize); - RenderTicketPtr GetRangeOfAudio(TimeRange range, bool prioritize); + RenderTicketPtr GetRangeOfAudio(TimeRange range, RenderTicketPriority prioritize); /** * @brief Set the viewer node to auto-cache @@ -110,8 +111,8 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(const QByteArray& hash, const rational &time, bool prioritize, bool texture_only); - RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms, bool prioritize); + RenderTicketWatcher *RenderFrame(const QByteArray& hash, const rational &time, RenderTicketPriority priority, bool texture_only); + RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms, RenderTicketPriority priority); /** * @brief Process all changes to internal NodeGraph copy diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 772048134..6fe161f2d 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -92,7 +92,7 @@ QByteArray RenderManager::Hash(const Node *n, const Node::ValueHint &output, con RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, - FrameHashCache* cache, bool prioritize, bool texture_only) + FrameHashCache* cache, RenderTicketPriority priority, bool texture_only) { return RenderFrame(viewer, color_manager, @@ -105,7 +105,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* c VideoParams::kFormatInvalid, nullptr, cache, - prioritize, + priority, texture_only); } @@ -115,7 +115,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* c const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output, - FrameHashCache* cache, bool prioritize, bool texture_only) + FrameHashCache* cache, RenderTicketPriority priority, bool texture_only) { // Create ticket RenderTicketPtr ticket = std::make_shared(); @@ -137,17 +137,17 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* c ticket->setProperty("cache", cache->GetCacheDirectory()); } - AddTicket(ticket); + AddTicket(ticket, priority); return ticket; } -RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange& r, RenderMode::Mode mode, bool generate_waveforms, bool prioritize) +RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange& r, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority) { - return RenderAudio(viewer, r, viewer->GetAudioParams(), mode, generate_waveforms, prioritize); + return RenderAudio(viewer, r, viewer->GetAudioParams(), mode, generate_waveforms, priority); } -RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms, bool prioritize) +RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority) { // Create ticket RenderTicketPtr ticket = std::make_shared(); @@ -159,12 +159,12 @@ RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange ticket->setProperty("enablewaveforms", generate_waveforms); ticket->setProperty("aparam", QVariant::fromValue(params)); - AddTicket(ticket); + AddTicket(ticket, priority); return ticket; } -RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr frame, const QByteArray &hash, bool prioritize) +RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr frame, const QByteArray &hash, RenderTicketPriority priority) { // Create ticket RenderTicketPtr ticket = std::make_shared(); @@ -174,7 +174,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr ticket->setProperty("hash", hash); ticket->setProperty("type", kTypeVideoDownload); - AddTicket(ticket); + AddTicket(ticket, priority); return ticket; } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index c5d4fac12..0206d3728 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -74,36 +74,30 @@ public: * The ticket from this function will return a FramePtr - the rendered frame in reference color * space. * - * Setting `prioritize` to TRUE puts this ticket at the top of the queue. Leaving it as FALSE - * appends it to the bottom. - * * This function is thread-safe. */ RenderTicketPtr RenderFrame(ViewerOutput *viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, - FrameHashCache* cache = nullptr, bool prioritize = false, bool texture_only = false); + FrameHashCache* cache = nullptr, RenderTicketPriority priority = RenderTicketPriority::kNormal, bool texture_only = false); RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, const VideoParams& video_params, const AudioParams& audio_params, const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output, - FrameHashCache* cache = nullptr, bool prioritize = false, bool texture_only = false); + FrameHashCache* cache = nullptr, RenderTicketPriority priority = RenderTicketPriority::kNormal, bool texture_only = false); /** * @brief Asynchronously generate a chunk of audio * * The ticket from this function will return a SampleBufferPtr - the rendered audio. * - * Setting `prioritize` to TRUE puts this ticket at the top of the queue. Leaving it as FALSE - * appends it to the bottom. - * * This function is thread-safe. */ - RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms, bool prioritize = false); - RenderTicketPtr RenderAudio(ViewerOutput *viewer, const TimeRange& r, RenderMode::Mode mode, bool generate_waveforms, bool prioritize = false); + RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority = RenderTicketPriority::kNormal); + RenderTicketPtr RenderAudio(ViewerOutput *viewer, const TimeRange& r, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority = RenderTicketPriority::kNormal); - RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, bool prioritize = false); + RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, RenderTicketPriority priority = RenderTicketPriority::kNormal); virtual void RunTicket(RenderTicketPtr ticket) const override; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 087bb90df..110306af8 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -546,7 +546,7 @@ void ViewerWidget::QueueNextAudioBuffer() RenderTicketWatcher *watcher = new RenderTicketWatcher(this); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForPlayback); audio_playback_queue_.push_back(watcher); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), true)); + watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), RenderTicketPriority::kHigh)); audio_playback_queue_time_ = queue_end; } @@ -694,7 +694,7 @@ void ViewerWidget::UpdateTextureFromNode() ClearVideoAutoCacherQueue(); } - watcher->SetTicket(GetFrame(time, true)); + watcher->SetTicket(GetFrame(time, RenderTicketPriority::kHigh)); } else { // There is definitely no frame here, we can immediately flip to showing nothing nonqueue_watchers_.clear(); @@ -769,7 +769,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) for (int i=0; iSetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), true)); + watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); } } } @@ -907,7 +907,7 @@ void ViewerWidget::SetDisplayImage(QVariant frame) } } -void ViewerWidget::RequestNextFrameForQueue(bool prioritize, bool increment) +void ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool increment) { rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, timebase()); @@ -921,11 +921,11 @@ void ViewerWidget::RequestNextFrameForQueue(bool prioritize, bool increment) watcher->setProperty("time", QVariant::fromValue(next_time)); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); queue_watchers_.append(watcher); - watcher->SetTicket(GetFrame(next_time, prioritize)); + watcher->SetTicket(GetFrame(next_time, priority)); } } -RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool prioritize) +RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority priority) { QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t); @@ -933,7 +933,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool prioritize) if (cached_hash.isEmpty() || !QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job - return auto_cacher_.GetSingleFrame(t, prioritize); + return auto_cacher_.GetSingleFrame(t, priority); } else { // Frame has been cached, grab the frame RenderTicketPtr ticket = std::make_shared(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index f99202cb7..d58730bf1 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -190,9 +190,9 @@ private: void SetDisplayImage(QVariant frame); - void RequestNextFrameForQueue(bool prioritize = false, bool increment = true); + void RequestNextFrameForQueue(RenderTicketPriority priority = RenderTicketPriority::kNormal, bool increment = true); - RenderTicketPtr GetFrame(const rational& t, bool prioritize); + RenderTicketPtr GetFrame(const rational& t, RenderTicketPriority priority); void FinishPlayPreprocess(); From 79e1ea70a5ac735d84ee2378345acec54517968f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 22:31:34 -0700 Subject: [PATCH 56/62] nodes: prevent transform traversals creeping back up --- app/node/traverser.cpp | 25 +++++++++++++++---------- app/node/traverser.h | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index a7738ba20..1b179f5fa 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -145,6 +145,7 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No { transform_ = transform; transform_start_ = start; + transform_now_ = nullptr; GenerateTable(end, range); @@ -211,7 +212,9 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); // Value will equal something from the connected node, follow it - return GenerateTable(node->GetConnectedOutput(input), adjusted_range); + Node *output = node->GetConnectedOutput(input); + NodeValueTable table = GenerateTable(output, adjusted_range, node); + return table; } else { @@ -229,7 +232,8 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range); if (node->IsInputConnected(input, i)) { - sub_tbl = GenerateTable(node->GetConnectedOutput(input, i), adjusted_range); + Node *output = node->GetConnectedOutput(input, i); + sub_tbl = GenerateTable(output, adjusted_range, node); } else { QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i); sub_tbl.Push(node->GetInputDataType(input), input_value, node); @@ -272,7 +276,7 @@ public: }; -NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range) +NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range, const Node *next_node) { // NOTE: Times how long a node takes to process, useful for profiling. //GTTTime gtt(n);Q_UNUSED(gtt); @@ -307,18 +311,19 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang NodeGlobals globals = GenerateGlobals(video_params_, range); n->Value(row, globals, &table); + // `transform_now_` is the next node in the path that needs to be traversed. It only ever goes + // "down" the graph so that any traversing going back up doesn't unnecessarily transform + // from unrelated nodes or the same node twice if (transform_) { - if (!transform_start_) { - if (!transform_ignore_.contains(n)) { + if (transform_now_ == n || transform_start_ == n) { + if (transform_now_ == n) { QTransform t = n->GizmoTransformation(row, globals); if (!t.isIdentity()) { (*transform_) *= t; } - - transform_ignore_.append(n); } - } else if (transform_start_ == n) { - transform_start_ = nullptr; + + transform_now_ = next_node; } } @@ -344,7 +349,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR NodeValueTable table; if (active_block) { - table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range)); + table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range), track); } return table; diff --git a/app/node/traverser.h b/app/node/traverser.h index 9471fec0f..afe56db2b 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -37,7 +37,7 @@ class NodeTraverser public: NodeTraverser(); - NodeValueTable GenerateTable(const Node *n, const TimeRange &range); + NodeValueTable GenerateTable(const Node *n, const TimeRange &range, const Node *next_node = nullptr); NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); @@ -157,8 +157,8 @@ private: const QAtomicInt *cancel_; const Node *transform_start_; + const Node *transform_now_; QTransform *transform_; - QVector transform_ignore_; }; From d4987c3a78795935021a8f77fd44a721002b1aa3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 22:38:46 -0700 Subject: [PATCH 57/62] ci: update brew before installing ninja --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bd345ed3..4f1848461 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,11 +343,16 @@ jobs: if: github.event_name == 'push' continue-on-error: true + - name: Install Ninja + shell: bash + run: | + brew update + brew install ninja + - name: Configure CMake shell: bash working-directory: ${{ runner.workspace }}/build run: | - brew install ninja PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \ From bb5ce4291625fa4a3fa8d4b759585175d973d20c Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Mon, 16 May 2022 10:07:28 +0200 Subject: [PATCH 58/62] new subtitles icon --- .../olive-dark/png/subtitles.128.disabled.png | Bin 0 -> 953 bytes app/ui/style/olive-dark/png/subtitles.128.png | Bin 0 -> 935 bytes .../olive-dark/png/subtitles.16.disabled.png | Bin 0 -> 526 bytes app/ui/style/olive-dark/png/subtitles.16.png | Bin 0 -> 378 bytes .../olive-dark/png/subtitles.32.disabled.png | Bin 0 -> 588 bytes app/ui/style/olive-dark/png/subtitles.32.png | Bin 0 -> 441 bytes .../olive-dark/png/subtitles.64.disabled.png | Bin 0 -> 632 bytes app/ui/style/olive-dark/png/subtitles.64.png | Bin 0 -> 581 bytes app/ui/style/olive-dark/svg/subtitles.svg | 315 ++++++++++++++++++ .../png/subtitles.128.disabled.png | Bin 0 -> 1039 bytes .../style/olive-light/png/subtitles.128.png | Bin 0 -> 1062 bytes .../olive-light/png/subtitles.16.disabled.png | Bin 0 -> 549 bytes app/ui/style/olive-light/png/subtitles.16.png | Bin 0 -> 436 bytes .../olive-light/png/subtitles.32.disabled.png | Bin 0 -> 598 bytes app/ui/style/olive-light/png/subtitles.32.png | Bin 0 -> 474 bytes .../olive-light/png/subtitles.64.disabled.png | Bin 0 -> 664 bytes app/ui/style/olive-light/png/subtitles.64.png | Bin 0 -> 627 bytes app/ui/style/olive-light/svg/subtitles.svg | 315 ++++++++++++++++++ 18 files changed, 630 insertions(+) create mode 100644 app/ui/style/olive-dark/png/subtitles.128.disabled.png create mode 100644 app/ui/style/olive-dark/png/subtitles.128.png create mode 100644 app/ui/style/olive-dark/png/subtitles.16.disabled.png create mode 100644 app/ui/style/olive-dark/png/subtitles.16.png create mode 100644 app/ui/style/olive-dark/png/subtitles.32.disabled.png create mode 100644 app/ui/style/olive-dark/png/subtitles.32.png create mode 100644 app/ui/style/olive-dark/png/subtitles.64.disabled.png create mode 100644 app/ui/style/olive-dark/png/subtitles.64.png create mode 100644 app/ui/style/olive-dark/svg/subtitles.svg create mode 100644 app/ui/style/olive-light/png/subtitles.128.disabled.png create mode 100644 app/ui/style/olive-light/png/subtitles.128.png create mode 100644 app/ui/style/olive-light/png/subtitles.16.disabled.png create mode 100644 app/ui/style/olive-light/png/subtitles.16.png create mode 100644 app/ui/style/olive-light/png/subtitles.32.disabled.png create mode 100644 app/ui/style/olive-light/png/subtitles.32.png create mode 100644 app/ui/style/olive-light/png/subtitles.64.disabled.png create mode 100644 app/ui/style/olive-light/png/subtitles.64.png create mode 100644 app/ui/style/olive-light/svg/subtitles.svg diff --git a/app/ui/style/olive-dark/png/subtitles.128.disabled.png b/app/ui/style/olive-dark/png/subtitles.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..74382a23788ab84f8c1aea9df8acbe78783641f6 GIT binary patch literal 953 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_GhdD9BkattCyw$HEDRdIEGZ*dOO$9pUF{z^?u{R z1%_Ep-kC{BZ~yDBUE48rqw+En+V5dd0ERfMs>gX``YbyjQL`xZTh~YH@E!e zYp)GE@y%ZN%r@qj3t{d{@8)s@ocsJgdAs6)`r4=OmpwLJ5FpbawxEz9s^Qs^_f4;+ zg`HWv^JabhNA&}Y8H`fw3s^H?^2cZ;Fzeob$y>{2W~>YDEx)t(+G@Gg+h6RADO}xrjhG)!vXFH#H7Vj)sTm6Ayd1pB9?9-}MQ-3fV z=KE+-s5kvZXji>>0nkfuCqH6dBfMV9Y`XUst&fax?6F@bSxi6e{l=lD!F_Gj`pB1x znR?ULmGa9jsJc9N-glX;Tj!OE?^~PuAx6*o+FRv^`dePbe%EYUS-ijdJ}XkNXxcfv zf8Ma~{i9#6Me3Pg0j0)rBgN*_{l%cbzU&7M^bao9vQx`|p}uK>aX|J?(FM0M0zI0d zx<5z@AN_x%4HUP)2%aJIli|8!_=@ITF4n+gBvRrUQQ}xyl96A;;G0-fl9{IvR9c*! zU$*`9nVCR&Rgip2Vo9o1a#1RfVlXl=GSW3L)ipE=F|e>QFtai=)HX1%GBEhMQ6nEk zLvDUbW?Cg~4J~gbTn1{81lbUrpH@mmtT}V`<;yxP?0f2 zQE5?fDnmv|Nr9EVesX?pZhl^|UU_DAW^QUqW+F(rK8U9ul9-pCAD@|=pBFb@V**e+ NgQu&X%Q~loCIH9jXZipD literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.128.png b/app/ui/style/olive-dark/png/subtitles.128.png new file mode 100644 index 0000000000000000000000000000000000000000..28e2e278da53b282ff677613c3cb8bf42a96d83d GIT binary patch literal 935 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9Fx?I6t9|MX)jP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdt~FBFEB_jQk=7-^8Ml%shpl z(&Fs=vhAnO%mm6CLkuY`N={|SC@Cqh($`PU&&|!xE7mK|%+AbBP036I8K)28>4zld zrRT?ICgyD~XCY9EYavU9noFyW9|?Y%=^ z%qrP_n}|7HU>Eqr#T}Ro-rww^fGv3cfP>%#xf!`~ghQ@vCD_rV;A2`ZkG zAZW|XQ}^Fx%u3_7VEEQ3o3Q)Zk}YE42P_M?=1d7%w_EOtq{C$s|Ay-aK3`n6d{)|l zM9rtSKgq6T5Uc;0U+~%dEKkM8KVAjvA2+h^_&NK9<>$S77iu^B{rG%Q=az5Vv>WDf z312jcs=h7SF#nr*cy|77rT_tEj&z2HJPLCdTXx%jVOTmP{q>BFyycm*{GI2$*LJW# zNEr0)&62v%^xpu z#b4K57mLmQC|0rO)|IF_hU%qL^rs%19d-XJ->vmh`Hb#s(v9U9^|kxVx9)o_yTpxQ z_tgHa=RPho`xTYNvfXv*MgXy@`XWcxykp2?+1?R@)a`fNv=zKlMQn1rK)zRza<-v zeYKy%cTF}88t$-w<^8w6A#{KHtGth*py+@Jm$DXw){3oqdi@h`EaRN;86C?$7XEBo z$Ka>!<8nFUZEiS&`hlE3J6CLy1Vy)~gXU8yiz6InCwVMl-n`{qJHO@cMU%5)1q^SP ztq*+5xO`b+-HiHq)~tTJ%;KSmPQ~+*+Hc;%oQg@8EEhxr^9O^ctDnm{r-UW|)^&+w literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.16.disabled.png b/app/ui/style/olive-dark/png/subtitles.16.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..923c94072afb3de7b36b850a451948f868885772 GIT binary patch literal 526 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`(o#gc;S3@UI05vX^-Jy0SlG72sgwIJD@?9iULHr;B5V#`(P$H*zr<3bZ`b zH@wSvJu0LA!SDSo{t4?9Zgf0|e0lVc^W`HYc{@LEw%+W$*r{)tlk3^Mlc$~?Z*Ugc zxc)@*4276%=?>k=^$eGCmQU_^VRuTohc!XlA}QA8#?%8x56Rv?a6ZZP-Q4HjR_<{6 z&!)x`+8_Kma_yA3Q@^CeBqlzb3Ur7_iEBiOV`)i7ei4IjVo^zEooc5!lIL8@MUQTpt6Hc~)E#t=oNMaiiQ z86_nJR{HwM`MJ6IdBu9=nc11SsVSL>Am#cXo_~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC|W}i(`mI@7>9YLWc}QT<*7bNVFvz!damK2fP~FDuA`h6 zGI)&VFo*EQ`drFX&<>y0`V~@tW>)3gy2@>RQ0>ae%1?|yH#2y;`njxg HN@xNA>^F;y literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.32.disabled.png b/app/ui/style/olive-dark/png/subtitles.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..aa143de89c6e4dfbcb3207b5080eb0c986cfb3ea GIT binary patch literal 588 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yhz`@43b?Ff=pwMnl7sn8f<8P5opE64tD`9l zb83<|#xPAkb@rc4lZ)x~rHejFB-GATnx1v)9;0~~L&-$%tEr4h2A0z=^ZY285mZ}V zaD`DT;fibQrCOcptKD-dPA!P5v8-Z#5ukQluWa*Epf^NHTq8;xOG`5Hix_+pi%K%{ z6oN{Nv-8WgpFT4aD6a~VPf09EwMs5Z1yT$~21Z7@2Bx}(W+4U^Rt9EP2A0|e237_J zTwl_rp=ij>PsvQH#I2!v9gP2 zNC6cYLll)3C8siEl#~=$>FX!w=jP_;73-B}W@qN6rer39lG|=Q$@zJ4 T^ED;_wKI6S`njxgN@xNAn?TAh literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.32.png b/app/ui/style/olive-dark/png/subtitles.32.png new file mode 100644 index 0000000000000000000000000000000000000000..67038dcfa494ee9d9da4974a6e277cd0d03dcce0 GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?5GL=a}2dU(e+prB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesQtO8i(^Pc>)Yvjc@G%~wC;D-NaA#1=Cx&Za6BnBaYlQasYd3d zbQTxkRL)6Fi@l!wxv{MC@{iZ=;?{hB(=EcTWUS1vY8T_V14237Tl<+A%oPk@N!^Gxk$-lJZ=K*5UZM79hkxzScDTD*yZzbXO`PilV;Y+S zpYxq{&l67DKY_>3A@p)xz89Oiz?C~YCQUW|G5x|{!<`JP9iGWQP*ieN*xxZ%e*Yi) Vm#c5h%?Em!!PC{xWt~$(698~~sqX*) literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.64.disabled.png b/app/ui/style/olive-dark/png/subtitles.64.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..22278781a879d54b9aea4d4c57c690791786fab0 GIT binary patch literal 632 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fP1vuEabkEalYaqsQS-CWH90<6#3 zHu@z_)H=BS#Q*6lKXD4J77@FqG($=7=mtBcLRHJ)d#RSi(_I4`IUf2jWH7l4EfOux z=K6GD3zIq@-}L?M(r*|%WFEz@^lvn6zb{zI)VG6s53ha9KIt2C%*_}%Jsg;XCN!|9 zC_tD7@!qp-H4EP_P~u)1ID64`>w2cc9W$=1@xAzduC74;9G!0Yw*~hZId3psJ>hvY z=nP-9g0x9!N?NX!-Da<6#R^km>{sfh9=86V&y;U%@TcqRXW<(i4sR#LmNT_>J21W# zdiLPs;X_YvIdv%VACuO4KH2V21klSOC9V-Aj-@3T`9%!AiA5!uc?vgcyqV(DCY@~pSj3J6ji;`0r zGD=Dctn~Gh^K*0a^NRJ#GqW>uQ&Tb%LCW<(JpGWwy!8C|%;fyMxcM3rfZ7>6UHx3v IIVCg!0LxC%tpET3 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/subtitles.64.png b/app/ui/style/olive-dark/png/subtitles.64.png new file mode 100644 index 0000000000000000000000000000000000000000..9e5d306bb0572c7015ed25d314fae177f3deb41e GIT binary patch literal 581 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq}8u)5S5QV$R#!hCxh*608rt8%Jd?|0Z|v+9B-DbjJWKn=nI#YnUvSyUx0H=}aC&g4ms#r=B$?tlxZP zg_ueF0nfaKnX&$YDLU`|O|9DVGrWN-fjuFZ@eMv`T5eYa-tXVCk`6Up%6%$wQ=d!jCu79TqO_@cMR?&-(w{AE6n z`mD0rc(?A1aP4h*Me(Znjm~C;p%&}Ia_%m9cAgPrAUhIkbea%bs$G`^iAI)a{2ZfQ=JFB0xhYV quYXXnh`(?4v7NI_02og857?8Q_}hNEv?>4?dkmhgelF{r5}E+w$?Q1* literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/svg/subtitles.svg b/app/ui/style/olive-dark/svg/subtitles.svg new file mode 100644 index 000000000..9e1eb0d2e --- /dev/null +++ b/app/ui/style/olive-dark/svg/subtitles.svg @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/png/subtitles.128.disabled.png b/app/ui/style/olive-light/png/subtitles.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e79ddd52e2d4756e19c61a096a4ed2163c714a0b GIT binary patch literal 1039 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_GhdD9Bdqi7Jaz`)Kur`;uunK>+RgV{>id6r>&1M5p_&H*G;6lFu9~>$$n}L?yoWlA_Z{K?@ZNXZqsRAb z{Zv#ANhr4nIB_Vp2rR05PA7Tzh5Wxbv1# z!`rjp|G&&yE&IW({?ql~wcEl^uXAS1V3cBAz?#wKD}H9*rLMv`H8=lNyf1HHy1-<@ z<-mPGgF)9}F~b&yUYZH?)*FABlk>!A@-N4~i_4zA?_Pbn{X*f!oO$Ke2dx#Pcb@(e*sf$74v&!Ey#yp!{e)dA@+G9t9jNjj5o>4NryXID2jE-H!vgu&`=GxEKZ90B*Q{p|fs6TCG8x8K?me?j+9`Qv7 zDTI9AGn?$=%h)A;J$w0gYX?}su9DUkH;jL_HE`{=T^o!1yh}Z|-pXRXb15h`Xzezh z31@Qizcrkl{PWk2KPF#q_1Dk7B6G1QR_DI&@~6Ki_uKjX+iJBhQt#{qzWM(i_ik_2 zJrn=ic&0D^zi(?GDaEN{`dRxL&0bZ#1uKDRn?a<+HKN3^v?L?Hh`~3ps3bE_A*i%C zJHKrE=`%Bd@~R;Dl*E!$tK_0oAjM#0U}U6gV5)0q7GhvwWngAyV4-bbU}a#ira4y$ zMMG|WN@iLmZVh+uU7ZKiAPKS|I6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=_ zh@#S>4zld zrRT?ICgEaktG3V{wy;&lG673&9%b7mV&|qQp+{i7cct)0ET2pJn z?0dJQ4H7aZTMJxbKjOr)b`8sWg|-zF&zf~PK62m0*!lTZj;Zw|-?V2u@BaUsYJBta z!Agm;>8&k5*Fiyo+0FfVPb%%qrFy5`{X1{w-Gc9*HO1Si&b+yB_x|jgpLI@OGdDA< z3;!2;^(YXmb`4y!^LNnh(=J?T+a#0s?BBch@75aqY5G7Tjz2svRvxd_m85(A^UhCu zI~k7k*YedL+wuEShDnrO{CtiBci%6K6bf2xb-R<{%p12^PZhSV+Pt=k=|f({_e^#eZ);&%L= zds*h#57X_*J^s~u?`_@o{Piu*@6q9}8IMSW-}zg_>>S%=Xy;=Qe|_1l{8-I<%y+V~ z6w2qPtvUAW^{pwt*Dn3aAXUc`?_(2xz3*;*Yz-HN_qWP5%#<~;xU>EU(Bd0^uT6c_ zy!q<@6)4T*mnKyLbJ;XMy5iH}JF{_J3D*{Y+e|z&C%x zBl~9VE&0B?`QQWZrk}gY76HwA@}0rj+Iqe-2Y=9+t4C}88{4shA_pbhv+DUTy#H=* zwo#y@SsgioW^)`k!?!zv<5qgw;!j&U8Seb81;)8)`Tu}@VP9D)=A~JBtbLWE&U9i^ z&fPO+mvuN-&bXO2E%o)&*XgIk%gW3DpE-YiKcC_q9>o~XjTT!@pD|1B`M!PIwz_HW zeOUyYII63w*}1v7&-eB9?Q0d#n|*Vsz@eSn#JisL+&{D5c(Z!sG}~2HHL#SEaloaenW_ja-KUcwGIH z=4h3aUI;4C{PExZq4|{ZQk(4&zGfTZ&$pdIkUhVXO<6B zTq~OU+J0xaO#I#-|JBS-zldS-gN&&*r3U+}=hlgRo#SnH?1`4^1G#nUf$kG2ag8W( zEG@~%FJkabEGo&&QwS<8&dx8}e)`Nzpu8$bJ|(dv)hf9t6-Y4{85kMq8kp)DnuQox zSQ(gE8JKDt7+4t?oDJc5grXrgKP5A*61RpuoLl068YDqB1m~xflqVLYGL)B>>t*I; z7bhncr0V4trO$q6BL!4s3{g~Cl$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aQmzl; f>4zldrRT?ICggTe~DWM4fQ+2w$ literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/subtitles.16.png b/app/ui/style/olive-light/png/subtitles.16.png new file mode 100644 index 0000000000000000000000000000000000000000..e7a564f2a408cb74d272baa82d625fcba58a2ec5 GIT binary patch literal 436 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9G08$g&*{RsbBprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesQrWs5Fsw;W#cP2{2Wiex#Q%2_Ljp7Uhg_;(f3*4 z5F5j?%qSV@S<5P4|E>#W+3|bw%(E(Y85?HC$}``wWwy{~Vz8NPQ~dp{_<}E6-yc;! zA~D_a`SHU^=YH;EaOf%A$v)wZXVc8JUT5t)pXhm1P5AO_b~4k3b+T29`0t+y&<)&R Rmjd)HgQu&X%Q~loCIB-qt~CGv literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/subtitles.32.disabled.png b/app/ui/style/olive-light/png/subtitles.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d3c075f563b525796fbd5132c6cda0465f9827a0 GIT binary patch literal 598 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yhz`@3*Ds}TVQ0Skt zEG^LBYf^Zn@Z*1d+vLSNLIm#|adIqqX}M&ZZ*E)Kstem?^dHuib1HuLE_9=(Gn8r3 zDdkGZ3$lwR+~X?d6KpwpP|er!!rJ8jt!q^kc9t=(;dT(Qv13kP_`;d+Doyk>i^Kfs zTZ(*d-VJNW5o8GbHZ}XyyKVB0b6)*kI;T{8f$Le>>ml#s5L5GiquC~+(; z$;dBa@J%c#$;?v-DlN{=FWY|l%uJxXDo8#hu_VKdAb7+6>t zm;sTtfq|8Q!Jj>1pg2Kj$jwj5OsmALq4(js!$1v^ARB`7(@M${i&7cN%ggmL^RkPR z6AM!H@{7`Ezq647Dl&#BDlJM*WymNgDX`MlPtMQH&Ce^=E6>c%%uP+nOav*{2l4bn d67$mY<1>@<^Wx@fOaN+U@O1TaS?83{1ORDG%rO7} literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/subtitles.32.png b/app/ui/style/olive-light/png/subtitles.32.png new file mode 100644 index 0000000000000000000000000000000000000000..f38d36eff3fb769e069ed69834bdd435d96a5393 GIT binary patch literal 474 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?5GL=a}2dU(e+prB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq{|3)5S3)qV?@GMZY5s5^ne1XYo$Iu|-tEhMl4D34?|V=e*zq zLyL0_M>fn-PFTh}Jt6!<@{E)j+b+jmJb0$Ke!t(o>*rK-MgB=LZpn>io}N}VWogJS zwz#>)wX6wg=MPx9|IC*^U|R5QzD0E90jULD>%FR?HeEfFceqmF!JO)dpaq$;?3p6@ zV!M}ZU=W* zXHMVA-@#0~Gw!KvvfLJY@QD~h`J{7y64KrW-;}v{>o3c$b+;L2J8yQDNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fP1vuE)gJfR91Z2>yxq#Nz4dR_WU-RFm2QP znF7w;JC~+7?lL^Uz~EMF+?xLNk=(K4j&%nt&9+F@DX#HhkeGdVf6&rfvVVl6)V(?8YD#^@K2r4bk&M(`3`pitAyeddOC9x#cD!C{XNHG{0 z7#ZmrnCcpug&0^^8JJlam}?stSQ!{Zot8a~q9HdwB{QuOw+8KLOUr>8BtbR==ckpF zCl;kLl$V$5W#(lUCnpx9>g5-u&wghk1yp1VQB+!#oXU_ont`Fkrha~2u=f`Iz=jX-E*O&m*&fw|l=d#Wzp$P!lbLT|> literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/subtitles.64.png b/app/ui/style/olive-light/png/subtitles.64.png new file mode 100644 index 0000000000000000000000000000000000000000..3fc6061b8f18e4a98afdfb07824c5346c1b99b16 GIT binary patch literal 627 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq`+Mr;B4q#hkad4E+upNU%Qm&#&N`#8A1==j;L2fH1K>2J;6( z9xIf6_PXRVq&uA1%jj%o<@z$L^I>}CVvenacQq;%_UCtrMAy}Ay5qH$Lr^eqOUdU6 z&wqMGPU}xA(^pDA;8Pp5>ecGvJgfQV_q)3EndM(HsWsZnF6f!~@z?aO6JN8oTCH96 z%GpIFbI+c`DNQTBM~Nq-J)fa-IQyMxrnE%!3vP>Vyc%7PvN{xMPi}g%`EBhN^#;}j z%nLR$lrWfxHSjeYW4ge2A%ek*L5kaf-65Ir1w)1ogDgWYrlLLjdH$No_2-9vb#>9( zwm1B0>rCISMHT!ruU}nPTHECBFiGY7rA?g1(reA7OxCZCef^6C=$OT}$71A+*00We z{mZ4EeFMDJuX`ObO=J#VL7Q&d~{dDpG2>-;}_Z}|HB&Dns@ zzm8c0U84JoX-|2>`}nZ`u^_kS$c1cmVv`IiUVm2j1KWwFp!Zw#4p^FpUY*&Yqd2i` nW60H+#d%$PPA)EA*#9utZpn0C#{D1w7}pG*u6{1-oD!M + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + From 713037169075a70d9423ddcc7754c7d1cd9d2184 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 16 May 2022 08:37:14 -0700 Subject: [PATCH 59/62] timeline: improved subtitle import behavior --- app/widget/timelinewidget/timelinewidget.cpp | 5 ---- app/widget/timelinewidget/timelinewidget.h | 5 ++-- app/widget/timelinewidget/tool/import.cpp | 6 ++++- .../timelinewidget/trackview/trackview.cpp | 3 --- .../timelinewidget/trackview/trackview.h | 4 ---- .../trackview/trackviewitem.cpp | 23 ------------------- .../timelinewidget/trackview/trackviewitem.h | 9 -------- 7 files changed, 8 insertions(+), 47 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index d46a35e9c..47c704a76 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -157,11 +157,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : connect(view, &TimelineView::DragLeft, this, &TimelineWidget::ViewDragLeft); connect(view, &TimelineView::DragDropped, this, &TimelineWidget::ViewDragDropped); - TrackView *tv = tview->track_view(); - connect(tv, &TrackView::DragEntered, this, &TimelineWidget::ViewDragEntered); - connect(tv, &TrackView::DragLeft, this, &TimelineWidget::ViewDragLeft); - connect(tv, &TrackView::DragDropped, this, &TimelineWidget::ViewDragDropped); - connect(tview->splitter(), &QSplitter::splitterMoved, this, &TimelineWidget::UpdateHorizontalSplitters); // Connect each view's scroll to each other diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 84d10e7fd..e2f9d6b35 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -109,8 +109,6 @@ public: void AddTentativeSubtitleTrack(); - void ClearTentativeSubtitleTrack(); - /** * @brief Timelines should always be connected to sequences */ @@ -271,6 +269,9 @@ public: }; +public slots: + void ClearTentativeSubtitleTrack(); + signals: void BlockSelectionChanged(const QVector& selected_blocks); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index a9198ec38..be98ab2ba 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -97,6 +97,11 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event) PrepGhosts(ghost_offset_, drag_start_.GetTrack().index()); if (parent()->HasGhosts() || !parent()->GetConnectedNode()) { + // We only clear the tentative track if the mimedata is about to be destroyed (i.e. the drag + // is cancelled). If we do this in DragLeave, it leads to undesirable behavior if the cursor + // is going between views (subtitle track rapidly appearing and disappearing) + QObject::connect(event->GetMimeData(), &QObject::destroyed, parent(), &TimelineWidget::ClearTentativeSubtitleTrack); + event->accept(); } else { event->ignore(); @@ -167,7 +172,6 @@ void ImportTool::DragLeave(QDragLeaveEvent* event) { if (!dragged_footage_.isEmpty()) { parent()->ClearGhosts(); - parent()->ClearTentativeSubtitleTrack(); dragged_footage_.clear(); event->accept(); diff --git a/app/widget/timelinewidget/trackview/trackview.cpp b/app/widget/timelinewidget/trackview/trackview.cpp index 09d2e0fbc..562c68d55 100644 --- a/app/widget/timelinewidget/trackview/trackview.cpp +++ b/app/widget/timelinewidget/trackview/trackview.cpp @@ -124,9 +124,6 @@ void TrackView::InsertTrack(Track *track) TrackViewItem *tvi = new TrackViewItem(track); connect(tvi, &TrackViewItem::AboutToDeleteTrack, this, &TrackView::AboutToDeleteTrack); - connect(tvi, &TrackViewItem::DragEntered, this, &TrackView::DragEntered); - connect(tvi, &TrackViewItem::DragLeft, this, &TrackView::DragLeft); - connect(tvi, &TrackViewItem::DragDropped, this, &TrackView::DragDropped); splitter_->Insert(track->Index(), track->GetTrackHeightInPixels(), diff --git a/app/widget/timelinewidget/trackview/trackview.h b/app/widget/timelinewidget/trackview/trackview.h index 9d56dabe0..1498a526b 100644 --- a/app/widget/timelinewidget/trackview/trackview.h +++ b/app/widget/timelinewidget/trackview/trackview.h @@ -43,10 +43,6 @@ public: signals: void AboutToDeleteTrack(Track *track); - void DragEntered(TimelineViewMouseEvent* event); - void DragLeft(QDragLeaveEvent* event); - void DragDropped(TimelineViewMouseEvent* event); - protected: virtual void resizeEvent(QResizeEvent *e) override; diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index 5eafc077d..6a5ba698e 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -76,34 +76,11 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) : setMinimumHeight(mute_button_->height()); setContextMenuPolicy(Qt::CustomContextMenu); - setAcceptDrops(true); connect(track, &Track::MutedChanged, mute_button_, &QPushButton::setChecked); connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu); } -void TrackViewItem::dragEnterEvent(QDragEnterEvent *event) -{ - TimelineViewMouseEvent e(0, 1, 1, track_->ToReference(), Qt::NoButton, event->keyboardModifiers()); - e.SetMimeData(event->mimeData()); - e.SetEvent(event); - e.SetBypassImportBuffer(true); - emit DragEntered(&e); -} - -void TrackViewItem::dragLeaveEvent(QDragLeaveEvent *event) -{ - emit DragLeft(event); -} - -void TrackViewItem::dropEvent(QDropEvent *event) -{ - TimelineViewMouseEvent e(0, 1, 1, track_->ToReference(), Qt::NoButton, event->keyboardModifiers()); - e.SetMimeData(event->mimeData()); - e.SetEvent(event); - emit DragDropped(&e); -} - QPushButton *TrackViewItem::CreateMSLButton(const QColor& checked_color) const { QPushButton* button = new QPushButton(); diff --git a/app/widget/timelinewidget/trackview/trackviewitem.h b/app/widget/timelinewidget/trackview/trackviewitem.h index b4010cab0..fac93d4d7 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.h +++ b/app/widget/timelinewidget/trackview/trackviewitem.h @@ -42,15 +42,6 @@ public: signals: void AboutToDeleteTrack(Track *track); - void DragEntered(TimelineViewMouseEvent* event); - void DragLeft(QDragLeaveEvent* event); - void DragDropped(TimelineViewMouseEvent* event); - -protected: - virtual void dragEnterEvent(QDragEnterEvent *event) override; - virtual void dragLeaveEvent(QDragLeaveEvent *event) override; - virtual void dropEvent(QDropEvent *event) override; - private: QPushButton* CreateMSLButton(const QColor &checked_color) const; From 20d04af64bdd426fcf11529bd3d7d1c52bb34b94 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 16 May 2022 08:47:08 -0700 Subject: [PATCH 60/62] timeline: use sequence dimensions for width and height Fixes #1923 --- app/widget/timelinewidget/tool/add.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 2dc04f17a..866a384d2 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -105,13 +105,14 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) Sequence *s = parent()->sequence(); - // If we want to set a manual rect for something, we can do so here - // - //VideoParams svp = s->GetVideoParams(); - //QRectF r(0, 0, svp.width(), svp.height()); - //r.adjust(svp.width()/10, svp.height()/10, -svp.width()/10, -svp.height()/10); + QRectF r; + if (Core::instance()->GetSelectedAddableObject() == Tool::kAddableTitle) { + VideoParams svp = s->GetVideoParams(); + r = QRectF(0, 0, svp.width(), svp.height()); + r.adjust(svp.width()/10, svp.height()/10, -svp.width()/10, -svp.height()/10); + } - CreateAddableClip(command, s, ghost_->GetTrack(), ghost_->GetAdjustedIn(), ghost_->GetAdjustedLength()); + CreateAddableClip(command, s, ghost_->GetTrack(), ghost_->GetAdjustedIn(), ghost_->GetAdjustedLength(), r); Core::instance()->undo_stack()->push(command); } @@ -125,7 +126,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, const Track::Reference &track, const rational &in, const rational &length, const QRectF &rect) { ClipBlock* clip; - if (Core::instance()->GetSelectedAddableObject() == olive::Tool::kAddableSubtitle) { + if (Core::instance()->GetSelectedAddableObject() == Tool::kAddableSubtitle) { clip = new SubtitleBlock(); } else { clip = new ClipBlock(); From 99d9be43ad5ee0decc2f40d70c7ea7723d478341 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 16 May 2022 08:59:38 -0700 Subject: [PATCH 61/62] ui: use new subtitles icon --- app/node/project/footage/footage.cpp | 2 +- app/ui/icons/icons.cpp | 2 ++ app/ui/icons/icons.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 5ce6afab3..0acf544fd 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -273,7 +273,7 @@ QIcon Footage::icon() const } else if (s.is_valid() && s.video_type() == VideoParams::kVideoTypeStill) { return icon::Image; } else if (HasEnabledSubtitleStreams()) { - return icon::TextSmallCaps; // FIXME: Procure icon + return icon::Subtitles; } } diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 581235e37..85288cb6b 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -92,6 +92,7 @@ QIcon icon::EyeClosed; QIcon icon::LockOpened; QIcon icon::LockClosed; QIcon icon::Pencil; +QIcon icon::Subtitles; void icon::LoadAll(const QString& theme) { @@ -164,6 +165,7 @@ void icon::LoadAll(const QString& theme) LockClosed = Create(theme, "lock-closed"); Pencil = Create(theme, "text-edit"); + Subtitles = Create(theme, "subtitles"); } QIcon icon::Create(const QString& theme, const QString &name) diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index c331a1683..83a9f18dc 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -104,6 +104,7 @@ extern QIcon EyeClosed; extern QIcon LockOpened; extern QIcon LockClosed; extern QIcon Pencil; +extern QIcon Subtitles; /** * @brief Create an icon object loaded from file From 7ab3350c5966014f9e1dd70cb86551b635b4b8e7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 16 May 2022 09:06:49 -0700 Subject: [PATCH 62/62] timeline: ensure tentative subtitle command is taken when dropping onto null --- app/widget/timelinewidget/tool/import.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index be98ab2ba..bc1a5590a 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -384,6 +384,10 @@ void ImportTool::DropGhosts(bool insert) FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0); + if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) { + command->add_child(c); + } + sequence = new_sequence; // Set this as the sequence to open