From 393f0105dc419f669ddfdb1e99a289106e69459e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 6 Jun 2022 09:40:07 -0700 Subject: [PATCH 01/40] slider: queue drag timer start --- app/widget/slider/base/sliderladder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/slider/base/sliderladder.cpp b/app/widget/slider/base/sliderladder.cpp index 245c32ab9..747a14cf4 100644 --- a/app/widget/slider/base/sliderladder.cpp +++ b/app/widget/slider/base/sliderladder.cpp @@ -120,7 +120,7 @@ void SliderLadder::SetValue(const QString &s) void SliderLadder::StartListeningToMouseInput() { - drag_timer_.start(); + QMetaObject::invokeMethod(&drag_timer_, "start", Qt::QueuedConnection); } void SliderLadder::mouseReleaseEvent(QMouseEvent *event) From 6bc84e00aed175fbc8a2f308b2235db70ae9ba1d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 6 Jun 2022 20:17:28 -0700 Subject: [PATCH 02/40] viewer: add watcher destroyed slot Prevents crashes --- app/widget/viewer/viewer.cpp | 9 +++++++++ app/widget/viewer/viewer.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index b5726864c..9581454a1 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -530,6 +530,14 @@ void ViewerWidget::CreateAddableAt(const QRectF &f) } } +void ViewerWidget::HandleFirstRequeueDestroy() +{ + // Extra protection to ensure we don't reference a destroyed object + if (first_requeue_watcher_ == sender()) { + first_requeue_watcher_ = nullptr; + } +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -676,6 +684,7 @@ void ViewerWidget::ForceRequeueFromCurrentTime() RenderTicketWatcher *watcher = RequestNextFrameForQueue(); if (!first_requeue_watcher_) { first_requeue_watcher_ = watcher; + connect(first_requeue_watcher_, &RenderTicketWatcher::destroyed, this, &ViewerWidget::HandleFirstRequeueDestroy); } } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 6c58ed194..4a879c367 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -338,6 +338,8 @@ private slots: void CreateAddableAt(const QRectF &f); + void HandleFirstRequeueDestroy(); + }; } From e74bb34d9a1694c779a5f3915f89190bab363515 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:12:05 -0700 Subject: [PATCH 03/40] viewer: request new frames even when preprocessing --- app/widget/viewer/viewer.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 9581454a1..b769a0925 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -786,8 +786,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) prequeue_count_ = 0; for (int i=0; iqueue()->AppendTimewise({ts, frame}, playback_speed_); } - prequeue_count_++; - if (prequeuing_video_ && prequeue_count_ == prequeue_length_) { - prequeuing_video_ = false; - FinishPlayPreprocess(); + if (prequeuing_video_) { + prequeue_count_++; + + if (prequeue_count_ == prequeue_length_) { + prequeuing_video_ = false; + FinishPlayPreprocess(); + } else { + RequestNextFrameForQueue(); + } } } } From 046b1cc49841ae0f6cf1571858728d3430fc3e3b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 09:55:56 -0700 Subject: [PATCH 04/40] serializer: bypass bezier adj on keyframe type set --- app/node/keyframe.cpp | 10 +++++++--- app/node/keyframe.h | 1 + app/node/project/serializer/serializer220403.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index eb6d5dea1..8a30ec560 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -99,7 +99,7 @@ const NodeKeyframe::Type &NodeKeyframe::type() const void NodeKeyframe::set_type(const NodeKeyframe::Type &type) { if (type_ != type) { - type_ = type; + set_type_no_bezier_adj(type); if (type_ == kBezier) { // Set some sane defaults if this keyframe existed in the track and was just changed @@ -120,11 +120,15 @@ void NodeKeyframe::set_type(const NodeKeyframe::Type &type) } } } - - emit TypeChanged(type_); } } +void NodeKeyframe::set_type_no_bezier_adj(const Type &type) +{ + type_ = type; + emit TypeChanged(type_); +} + const QPointF &NodeKeyframe::bezier_control_in() const { return bezier_control_in_; diff --git a/app/node/keyframe.h b/app/node/keyframe.h index ffe5a7728..e81de91f6 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -98,6 +98,7 @@ public: */ const Type& type() const; void set_type(const Type& type); + void set_type_no_bezier_adj(const Type& type); /** * @brief For bezier interpolation, the control point leading into this keyframe diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 2075b6ce8..7dd4ae5c3 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -865,7 +865,7 @@ void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyfram } 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())); + key->set_type_no_bezier_adj(static_cast(attr.value().toInt())); } else if (attr.name() == QStringLiteral("inhandlex")) { key_in_handle.setX(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("inhandley")) { From e27c53bea827438c574911a1e6e36d0bd4395531 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 10:34:51 -0700 Subject: [PATCH 05/40] timeline: relink duplicated clips --- app/widget/timelinewidget/tool/pointer.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 7158cb906..d2bf263bb 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -689,6 +689,8 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) InsertGapsAtGhostDestination(command); } + QMap relinks; + // Now we can re-add each clip foreach (const GhostBlockPair& p, blocks_moving) { Block* block = p.block; @@ -696,7 +698,10 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) if (duplicate_clips) { // Duplicate rather than move // Place the copy instead of the original block - block = static_cast(Node::CopyNodeInGraph(block, command)); + Block *new_block = static_cast(Node::CopyNodeInGraph(block, command)); + relinks.insert(block, new_block); + block = new_block; + if (ClipBlock *new_clip = dynamic_cast(block)) { new_clip->waveform() = static_cast(p.block)->waveform(); } @@ -709,6 +714,18 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) p.ghost->GetAdjustedIn())); } + if (!relinks.empty()) { + for (auto it=relinks.cbegin(); it!=relinks.cend(); it++) { + for (auto jt=it.key()->links().cbegin(); jt!=it.key()->links().cend(); jt++) { + Node *link = *jt; + Node *copy_link = relinks.value(link); + if (copy_link) { + command->add_child(new NodeLinkCommand(it.value(), copy_link, true)); + } + } + } + } + // Adjust selections TimelineWidgetSelections new_sel = parent()->GetSelections(); new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment()); From cd85299df5801a03edb1abb46f5f263701628ebf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 11:43:50 -0700 Subject: [PATCH 06/40] pan: fixed issue where original value was passed through --- app/node/audio/pan/pan.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 220ba9585..77e194506 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -69,8 +69,6 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Create a sample job SampleBuffer samples = value[kSamplesInput].toSamples(); if (samples.is_allocated()) { - bool pushed_job = false; - // This node is only compatible with stereo audio if (samples.audio_params().channel_count() == 2) { // If the input is static, we can just do it now which will be faster @@ -83,15 +81,14 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV samples.transform_volume_for_channel(1, 1.0f + pan_volume); } } + + table->Push(NodeValue(NodeValue::kSamples, samples, this)); } else { // Requires job - - pushed_job = true; table->Push(NodeValue::kSamples, SampleJob(kSamplesInput, value), this); } - } - - if (!pushed_job) { + } else { + // Pass right through table->Push(value[kSamplesInput]); } } From 6b13d806c692ad6f91c516e137102e4d1e326d33 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 11:44:12 -0700 Subject: [PATCH 07/40] nodeparamview: correctly show video and audio node options --- app/widget/nodeparamview/nodeparamview.cpp | 1 + .../nodeparamview/nodeparamviewcontext.cpp | 22 +++++++++++++++++-- .../nodeparamview/nodeparamviewcontext.h | 4 ++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 99a4cd95f..c3ab8be03 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -85,6 +85,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : NodeParamViewItemTitleBar *title_bar = static_cast(c->titleBarWidget()); if (i == Track::kVideo || i == Track::kAudio) { + c->SetEffectType(static_cast(i)); title_bar->SetAddEffectButtonVisible(true); title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast(i)))); } else { diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index 6e83a617b..1d3a22253 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -31,7 +31,8 @@ namespace olive { #define super NodeParamViewItemBase NodeParamViewContext::NodeParamViewContext(QWidget *parent) : - super(parent) + super(parent), + type_(Track::kNone) { QWidget *body = new QWidget(); QHBoxLayout *body_layout = new QHBoxLayout(body); @@ -126,13 +127,30 @@ void NodeParamViewContext::SetTime(const rational &time) } } +void NodeParamViewContext::SetEffectType(Track::Type type) +{ + type_ = type; +} + void NodeParamViewContext::Retranslate() { } void NodeParamViewContext::AddEffectButtonClicked() { - Menu *m = NodeFactory::CreateMenu(this, false, Node::kCategoryUnknown, Node::kVideoEffect); + Node::Flag flag = Node::kNone; + + if (type_ == Track::kVideo) { + flag = Node::kVideoEffect; + } else { + flag = Node::kAudioEffect; + } + + if (flag == Node::kNone) { + return; + } + + Menu *m = NodeFactory::CreateMenu(this, false, Node::kCategoryUnknown, flag); connect(m, &Menu::triggered, this, &NodeParamViewContext::AddEffectMenuItemTriggered); m->exec(QCursor::pos()); delete m; diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h index 256f772aa..11574e2a5 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.h +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -64,6 +64,8 @@ public: void SetTime(const rational &time); + void SetEffectType(Track::Type type); + signals: void AboutToDeleteItem(NodeParamViewItem *item); @@ -88,6 +90,8 @@ private: QVector items_; + Track::Type type_; + private slots: void AddEffectButtonClicked(); From 6391ef4a7f3d681acb6d9db53a4b541c35faf685 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:11:52 -0700 Subject: [PATCH 08/40] thread: removed unused var --- app/threading/threadpool.cpp | 1 - app/threading/threadpool.h | 1 - 2 files changed, 2 deletions(-) diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index 8b33d72db..d65f6c574 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -29,7 +29,6 @@ ThreadPool::ThreadPool(unsigned threads, QObject *parent) : threads = std::thread::hardware_concurrency(); } - available_count_ = threads; for (unsigned i = 0; i < threads; i += 1) { worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this, &tasks_, &task_mutex_, &cond_)); } diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index 3c1143f55..e4dcb18e1 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -62,7 +62,6 @@ private: std::condition_variable high_cond_; std::atomic_bool end_threadp_{false}; - std::atomic_int available_count_; }; From 09a3ce39b9735f187fdbaf063bbd4e6c477e3c5a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:12:03 -0700 Subject: [PATCH 09/40] rendermanager: reimplement clearing old decoders --- app/render/rendermanager.cpp | 24 ++++++++++++++++++++++++ app/render/rendermanager.h | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 9710aa626..e897c77e2 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -59,6 +59,11 @@ RenderManager::RenderManager(QObject *parent) : context_ = nullptr; decoder_cache_ = nullptr; } + + QTimer *decoder_clear_timer = new QTimer(this); + decoder_clear_timer->setInterval(kDecoderMaximumInactivity); + connect(decoder_clear_timer, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); + decoder_clear_timer->start(); } RenderManager::~RenderManager() @@ -157,4 +162,23 @@ void RenderManager::RunTicket(RenderTicketPtr ticket) const RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); } +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) { + qDebug() << "Cleared old decoder"; + decoder.decoder->Close(); + it = decoder_cache_->erase(it); + } else { + it++; + } + } +} + } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 2cf6b22a7..24d29e34a 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -130,6 +130,11 @@ private: ShaderCache* shader_cache_; + static constexpr auto kDecoderMaximumInactivity = 10000; + +private slots: + void ClearOldDecoders(); + }; } From b9119e21f43a9a1ce03b58ed56cdf1d9eefb42eb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:14:15 -0700 Subject: [PATCH 10/40] rendermanager: remove unnecessary debug line --- app/render/rendermanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index e897c77e2..80ccdaf70 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -172,7 +172,6 @@ void RenderManager::ClearOldDecoders() DecoderPair decoder = it.value(); if (decoder.decoder->GetLastAccessedTime() < min_age) { - qDebug() << "Cleared old decoder"; decoder.decoder->Close(); it = decoder_cache_->erase(it); } else { From 5ef56c80473c78c6016f8e698fe6ec25f8d6ecc8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 12 Jun 2022 10:28:15 -0700 Subject: [PATCH 11/40] ui: use QOpenGLWindow instead of QOpenGLWidget for all panels --- app/panel/audiomonitor/audiomonitor.cpp | 4 +-- app/widget/audiomonitor/audiomonitor.cpp | 15 ++++++---- app/widget/audiomonitor/audiomonitor.h | 6 ++-- app/widget/manageddisplay/manageddisplay.cpp | 23 +++++++++++++-- app/widget/manageddisplay/manageddisplay.h | 31 ++++++++++++++------ app/widget/scope/histogram/histogram.cpp | 2 +- app/widget/scope/waveform/waveform.cpp | 2 +- app/widget/viewer/viewerdisplay.cpp | 14 ++++----- app/widget/viewer/viewerdisplay.h | 1 - 9 files changed, 66 insertions(+), 32 deletions(-) diff --git a/app/panel/audiomonitor/audiomonitor.cpp b/app/panel/audiomonitor/audiomonitor.cpp index 84297447a..034ddcf9f 100644 --- a/app/panel/audiomonitor/audiomonitor.cpp +++ b/app/panel/audiomonitor/audiomonitor.cpp @@ -25,9 +25,9 @@ namespace olive { AudioMonitorPanel::AudioMonitorPanel(QWidget *parent) : PanelWidget(QStringLiteral("AudioMonitor"), parent) { - audio_monitor_ = new AudioMonitor(this); + audio_monitor_ = new AudioMonitor(); - setWidget(audio_monitor_); + setWidget(QWidget::createWindowContainer(audio_monitor_)); Retranslate(); } diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 8e7c945f5..588c39ac5 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -20,6 +20,7 @@ #include "audiomonitor.h" +#include #include #include @@ -35,8 +36,7 @@ const int kMaximumSmoothness = 8; QVector AudioMonitor::instances_; -AudioMonitor::AudioMonitor(QWidget *parent) : - QOpenGLWidget(parent), +AudioMonitor::AudioMonitor() : waveform_(nullptr), cached_channels_(0) { @@ -125,7 +125,10 @@ void AudioMonitor::SetUpdateLoop(bool e) void AudioMonitor::paintGL() { QPainter p(this); - p.fillRect(rect(), palette().window().color()); + QPalette palette = qApp->palette(); + QRect geometry(0, 0, width(), height()); + + p.fillRect(geometry, palette.window().color()); if (!params_.channel_count()) { return; @@ -137,12 +140,12 @@ void AudioMonitor::paintGL() int font_height = fm.height(); // Create rect where decibel markings will go on the side - QRect db_labels_rect = rect(); + QRect db_labels_rect = geometry; db_labels_rect.setWidth(QtUtils::QFontMetricsWidth(p.fontMetrics(), "-00")); db_labels_rect.adjust(0, font_height, 0, 0); // Determine rect where the main meter will go - QRect full_meter_rect = rect(); + QRect full_meter_rect = geometry; full_meter_rect.adjust(db_labels_rect.width(), font_height, 0, 0); // Width of each channel in the meter @@ -163,7 +166,7 @@ void AudioMonitor::paintGL() // Draw decibel markings QRect last_db_marking_rect; - cached_painter.setPen(palette().text().color()); + cached_painter.setPen(palette.text().color()); for (int i=0;i>=kDecibelMinimum;i-=kDecibelStep) { QString db_label; diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index d29b74491..adb36f4c1 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -22,7 +22,7 @@ #define AUDIOMONITORWIDGET_H #include -#include +#include #include #include "audio/audiovisualwaveform.h" @@ -32,11 +32,11 @@ namespace olive { -class AudioMonitor : public QOpenGLWidget +class AudioMonitor : public QOpenGLWindow { Q_OBJECT public: - AudioMonitor(QWidget* parent = nullptr); + AudioMonitor(); virtual ~AudioMonitor() override; diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 22589365e..1a8d66c08 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -61,11 +61,14 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : // Create OpenGL renderer attached_renderer_ = new OpenGLRenderer(this); + + // Create widget wrapper for OpenGL window + wrapper_ = QWidget::createWindowContainer(static_cast(inner_widget_)); + layout->addWidget(wrapper_); } else { inner_widget_ = nullptr; + wrapper_ = nullptr; } - - layout->addWidget(inner_widget_); } ManagedDisplayWidget::~ManagedDisplayWidget() @@ -253,6 +256,22 @@ void ManagedDisplayWidget::doneCurrent() } } +QPaintDevice *ManagedDisplayWidget::paint_device() const +{ + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + return static_cast(inner_widget_); + } else { + return nullptr; + } +} + +void ManagedDisplayWidget::SetInnerMouseTracking(bool e) +{ + if (wrapper_) { + wrapper_->setMouseTracking(e); + } +} + void ManagedDisplayWidget::update() { if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 94648c22e..4d85850ec 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -23,7 +23,7 @@ #include #include -#include +#include #include "node/color/colormanager/colormanager.h" #include "render/renderer.h" @@ -31,14 +31,11 @@ namespace olive { -class ManagedDisplayWidgetOpenGL : public QOpenGLWidget +class ManagedDisplayWidgetOpenGL : public QOpenGLWindow { Q_OBJECT public: - ManagedDisplayWidgetOpenGL(QWidget* parent = nullptr) : - QOpenGLWidget(parent) - { - } + ManagedDisplayWidgetOpenGL() = default; signals: void OnInit(); @@ -67,7 +64,7 @@ protected: { emit OnMouseMove(e); - QOpenGLWidget::mouseMoveEvent(e); + QOpenGLWindow::mouseMoveEvent(e); } private slots: @@ -188,11 +185,26 @@ protected: void doneCurrent(); - QWidget* inner_widget() const + QWindow* inner_widget() const { return inner_widget_; } + /** + * @brief Get inner widget as paint device for QPainter + * + * NOTE: This will be incompatible with QVulkanWindow so functions using it + * will need to be replaced soon. + */ + QPaintDevice *paint_device() const; + + void SetInnerMouseTracking(bool e); + + QRect GetInnerRect() const + { + return wrapper_ ? wrapper_->rect() : QRect(); + } + protected slots: /** * @brief Called whenever the internal rendering context has been created @@ -223,7 +235,8 @@ private: /** * @brief Main drawing surface abstraction */ - QWidget* inner_widget_; + QWindow* inner_widget_; + QWidget *wrapper_; /** * @brief Renderer abstraction diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index 26b92b711..a0ba2eaab 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -91,7 +91,7 @@ void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) renderer()->Blit(pipeline_secondary_, shader_job, texture_row_sums_->params()); // Draw line overlays - QPainter p(inner_widget()); + QPainter p(paint_device()); QFont font = p.font(); font.setPixelSize(10); QFontMetrics font_metrics = QFontMetrics(font); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index a0b11769c..bb6902a64 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -85,7 +85,7 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) float waveform_end_dim_x = (width() - 1.0) - waveform_start_dim_x; // Draw line overlays - QPainter p(inner_widget()); + QPainter p(paint_device()); QFont font; font.setPixelSize(10); QFontMetrics font_metrics = QFontMetrics(font); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index cdbe6fdc6..01bdee5e6 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -117,7 +117,7 @@ void ViewerDisplayWidget::UpdateCursor() void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e) { signal_cursor_color_ = e; - inner_widget()->setMouseTracking(e); + SetInnerMouseTracking(e); } void ViewerDisplayWidget::SetImage(const QVariant &buffer) @@ -511,7 +511,7 @@ void ViewerDisplayWidget::OnPaint() TimeRange range = GenerateGizmoTime(); gizmo_db_ = gt.GenerateRow(gizmos_, range); - QPainter p(inner_widget()); + QPainter p(paint_device()); gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, range); p.setWorldTransform(gizmo_last_draw_transform_); @@ -525,7 +525,7 @@ void ViewerDisplayWidget::OnPaint() // Draw action/title safe areas if (safe_margin_.is_enabled()) { - QPainter p(inner_widget()); + QPainter p(paint_device()); p.setWorldTransform(GenerateWorldTransform()); p.setPen(QPen(Qt::lightGray, 0)); @@ -575,7 +575,7 @@ void ViewerDisplayWidget::OnPaint() } if (frame_rate_average_count_ >= frame_rate_averages_.size()) { - QPainter p(inner_widget()); + QPainter p(paint_device()); double average = 0.0; for (int i=0; irect(), tr("%1 FPS").arg(QString::number(average, 'f', 1))); + DrawTextWithCrudeShadow(&p, GetInnerRect(), tr("%1 FPS").arg(QString::number(average, 'f', 1))); if (frames_skipped_ > 0) { - DrawTextWithCrudeShadow(&p, inner_widget()->rect().adjusted(0, p.fontMetrics().height(), 0, 0), + DrawTextWithCrudeShadow(&p, GetInnerRect().adjusted(0, p.fontMetrics().height(), 0, 0), tr("%1 frames skipped").arg(frames_skipped_)); } } @@ -597,7 +597,7 @@ void ViewerDisplayWidget::OnPaint() const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); if (!subtitle_tracklist.empty()) { - QPainter p(inner_widget()); + QPainter p(paint_device()); QTransform transform = GenerateWorldTransform(); QRect bounding_box = transform.mapRect(rect()); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 7359087f6..629a682aa 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -22,7 +22,6 @@ #define VIEWERGLWIDGET_H #include -#include #include #include "node/color/colormanager/colormanager.h" From 608cb05ae2e12fbc4f61e5b43db79b3cf5932960 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Jun 2022 07:44:19 -0700 Subject: [PATCH 12/40] qtutils: attempt to work around qt bug --- app/common/qtutils.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index e4cc56c28..8105de09b 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -70,7 +70,11 @@ QDateTime QtUtils::GetCreationDate(const QFileInfo &info) #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) return info.created(); #else - return info.birthTime(); + QDateTime t = info.birthTime(); + if (!t.isValid()) { + t = info.metadataChangeTime(); + } + return t; #endif } From 240bbdc5832a0ee1109abcf155ed565287d50e12 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Jun 2022 08:49:45 -0700 Subject: [PATCH 13/40] viewer: fix mouse and compositing issues from using windows --- app/panel/audiomonitor/audiomonitor.cpp | 19 +- app/panel/audiomonitor/audiomonitor.h | 2 + app/widget/manageddisplay/manageddisplay.cpp | 45 ++- app/widget/manageddisplay/manageddisplay.h | 16 +- app/widget/viewer/viewerdisplay.cpp | 383 ++++++++++--------- app/widget/viewer/viewerdisplay.h | 35 +- 6 files changed, 280 insertions(+), 220 deletions(-) diff --git a/app/panel/audiomonitor/audiomonitor.cpp b/app/panel/audiomonitor/audiomonitor.cpp index 034ddcf9f..2b2df6a28 100644 --- a/app/panel/audiomonitor/audiomonitor.cpp +++ b/app/panel/audiomonitor/audiomonitor.cpp @@ -20,18 +20,35 @@ #include "audiomonitor.h" +#include "panel/panelmanager.h" + namespace olive { +#define super PanelWidget + AudioMonitorPanel::AudioMonitorPanel(QWidget *parent) : - PanelWidget(QStringLiteral("AudioMonitor"), parent) + super(QStringLiteral("AudioMonitor"), parent) { audio_monitor_ = new AudioMonitor(); + audio_monitor_->installEventFilter(this); + setWidget(QWidget::createWindowContainer(audio_monitor_)); Retranslate(); } +bool AudioMonitorPanel::eventFilter(QObject *o, QEvent *e) +{ + if (o == audio_monitor_ && e->type() == QEvent::FocusIn) { + // HACK: QWindow focus isn't accounted for in QApplication::focusChanged, so we handle it + // manually here. + PanelManager::instance()->FocusChanged(nullptr, this); + } + + return super::eventFilter(o, e); +} + void AudioMonitorPanel::Retranslate() { SetTitle(tr("Audio Monitor")); diff --git a/app/panel/audiomonitor/audiomonitor.h b/app/panel/audiomonitor/audiomonitor.h index 2c76d60da..b9d7b5e04 100644 --- a/app/panel/audiomonitor/audiomonitor.h +++ b/app/panel/audiomonitor/audiomonitor.h @@ -45,6 +45,8 @@ public: audio_monitor_->SetParams(params); } + virtual bool eventFilter(QObject *o, QEvent *e) override; + private: virtual void Retranslate() override; diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 1a8d66c08..923d0bdee 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -23,18 +23,19 @@ #include #include +#include "panel/panelmanager.h" #include "render/opengl/openglrenderer.h" #include "render/rendermanager.h" namespace olive { +#define super QWidget + ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : QWidget(parent), color_manager_(nullptr), color_service_(nullptr) { - setContextMenuPolicy(Qt::CustomContextMenu); - QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); @@ -55,9 +56,7 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : &ManagedDisplayWidgetOpenGL::frameSwapped, this, &ManagedDisplayWidget::frameSwapped, Qt::DirectConnection); - connect(static_cast(inner_widget_), - &ManagedDisplayWidgetOpenGL::OnMouseMove, - this, &ManagedDisplayWidget::InnerWidgetMouseMove); + inner_widget_->installEventFilter(this); // Create OpenGL renderer attached_renderer_ = new OpenGLRenderer(this); @@ -279,6 +278,42 @@ void ManagedDisplayWidget::update() } } +bool ManagedDisplayWidget::eventFilter(QObject *o, QEvent *e) +{ + if (o != inner_widget_) { + return super::eventFilter(o, e); + } + + switch (e->type()) { + case QEvent::FocusIn: + // HACK: QWindow focus isn't accounted for in QApplication::focusChanged, so we handle it + // manually here. + PanelManager::instance()->FocusChanged(nullptr, this); + break; + case QEvent::ContextMenu: + { + QContextMenuEvent *ctx = static_cast(e); + emit customContextMenuRequested(ctx->pos()); + return true; + } + case QEvent::MouseButtonPress: + { + // HACK: QWindows don't seem to receive ContextMenu events on right click (only when pressing + // the menu button on the keyboard) so we handle it manually here + QMouseEvent *ev = static_cast(e); + if (ev->button() == Qt::RightButton) { + emit customContextMenuRequested(ev->pos()); + return true; + } + break; + } + default: + break; + } + + return super::eventFilter(o, e); +} + Menu* ManagedDisplayWidget::GetDisplayMenu(QMenu* parent, bool auto_connect) { QStringList displays = color_manager()->ListAvailableDisplays(); diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 4d85850ec..89a952eb0 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -38,14 +38,11 @@ public: ManagedDisplayWidgetOpenGL() = default; signals: + // Render signals void OnInit(); - void OnPaint(); - void OnDestroy(); - void OnMouseMove(QMouseEvent* e); - protected: virtual void initializeGL() override { @@ -60,13 +57,6 @@ protected: emit OnPaint(); } - virtual void mouseMoveEvent(QMouseEvent* e) override - { - emit OnMouseMove(e); - - QOpenGLWindow::mouseMoveEvent(e); - } - private slots: void DestroyListener() { @@ -132,6 +122,8 @@ public: */ void update(); + virtual bool eventFilter(QObject *o, QEvent *e) override; + public slots: /** * @brief Replaces the color transform with a new one @@ -156,8 +148,6 @@ signals: void frameSwapped(); - void InnerWidgetMouseMove(QMouseEvent* event); - protected: /** * @brief Provides access to the color processor (nullptr if none is set) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 01bdee5e6..7d6d660c9 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -73,8 +73,6 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : { connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged); - connect(this, &ViewerDisplayWidget::InnerWidgetMouseMove, this, &ViewerDisplayWidget::EmitColorAtCursor); - // Initializes cursor based on tool UpdateCursor(); @@ -242,185 +240,48 @@ void ViewerDisplayWidget::IncrementSkippedFrames() Core::instance()->ShowStatusBarMessage(tr("%n skipped frame(s) detected during playback", nullptr, frames_skipped_), 10000); } -void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) +bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e) { - if (event->button() == Qt::LeftButton && Core::instance()->tool() == Tool::kAdd - && (Core::instance()->GetSelectedAddableObject() == Tool::kAddableShape || Core::instance()->GetSelectedAddableObject() == Tool::kAddableTitle)) { + if (o != this->inner_widget()) { + return super::eventFilter(o, e); + } - 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_ - && (gizmo_last_draw_transform_inverted_ = 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(); - gizmo_last_drag_ = gizmo_start_drag_; - current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime())); - - } else if (IsHandDrag(event)) { - - // Handle hand drag - hand_last_drag_pos_ = event->pos(); - hand_dragging_ = true; - emit HandDragStarted(); - setCursor(Qt::ClosedHandCursor); - - } else { - - if (event->button() == Qt::LeftButton) { - // Handle standard drag - emit DragStarted(); + switch (e->type()) { + case QEvent::MouseButtonPress: + if (OnMousePress(static_cast(e))) { + return true; } - - super::mousePressEvent(event); - - } -} - -void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) -{ - // Handle hand dragging - if (hand_dragging_) { - - // Emit movement - emit HandDragMoved(event->x() - hand_last_drag_pos_.x(), - event->y() - hand_last_drag_pos_.y()); - - 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 - if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { - if (!gizmo_drag_started_) { - QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; - - rational gizmo_time = GetGizmoTime(); - NodeTraverser t; - t.SetCacheVideoParams(gizmo_params_); - NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base())); - - draggable->DragStart(row, start.x(), start.y(), gizmo_time); - gizmo_drag_started_ = true; - } - - QPointF v = event->pos() * gizmo_last_draw_transform_inverted_; - switch (draggable->GetDragValueBehavior()) { - case DraggableGizmo::kAbsolute: - // Above value is correct - break; - case DraggableGizmo::kDeltaFromPrevious: - v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_; - gizmo_last_drag_ = event->pos(); - break; - case DraggableGizmo::kDeltaFromStart: - v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; - break; - } - - draggable->DragMove(v.x(), v.y(), event->modifiers()); + break; + case QEvent::MouseMove: + EmitColorAtCursor(static_cast(e)); + if (OnMouseMove(static_cast(e))) { + return true; } - - } else { - - // Default behavior - super::mouseMoveEvent(event); - - } -} - -void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) -{ - if (hand_dragging_) { - - // Handle hand drag - emit HandDragEnded(); - hand_dragging_ = false; - UpdateCursor(); - - } else if (add_band_) { - - const QRect &band_rect = add_band_->geometry(); - if (band_rect.width() > 1 && band_rect.height() > 1) { - QRectF r = GenerateDisplayTransform().inverted().mapRect(add_band_->geometry()); - emit CreateAddableAt(r); + break; + case QEvent::MouseButtonRelease: + if (OnMouseRelease(static_cast(e))) { + return true; } - - add_band_->deleteLater(); - add_band_ = nullptr; - - } else if (current_gizmo_) { - - // Handle gizmo - if (gizmo_drag_started_) { - MultiUndoCommand *command = new MultiUndoCommand(); - if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { - draggable->DragEnd(command); - } - Core::instance()->undo_stack()->pushIfHasChildren(command); - gizmo_drag_started_ = false; - } - current_gizmo_ = nullptr; - - } else { - - // Default behavior - super::mouseReleaseEvent(event); - - } -} - -void ViewerDisplayWidget::mouseDoubleClickEvent(QMouseEvent *event) -{ - if (event->button() == Qt::LeftButton && gizmos_) { - QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos()); - foreach (NodeGizmo *g, gizmos_->GetGizmos()) { - if (TextGizmo *text = dynamic_cast(g)) { - if (text->GetRect().contains(ptr)) { - OpenTextGizmo(text, event); - break; - } - } + break; + case QEvent::MouseButtonDblClick: + if (OnMouseDoubleClick(static_cast(e))) { + return true; } + break; + case QEvent::DragEnter: + emit DragEntered(static_cast(e)); + break; + case QEvent::DragLeave: + emit DragLeft(static_cast(e)); + break; + case QEvent::Drop: + emit Dropped(static_cast(e)); + break; + default: + break; } - super::mouseDoubleClickEvent(event); -} - -void ViewerDisplayWidget::dragEnterEvent(QDragEnterEvent *event) -{ - emit DragEntered(event); - - if (!event->isAccepted()) { - super::dragEnterEvent(event); - } -} - -void ViewerDisplayWidget::dragLeaveEvent(QDragLeaveEvent *event) -{ - emit DragLeft(event); - - if (!event->isAccepted()) { - super::dragLeaveEvent(event); - } -} - -void ViewerDisplayWidget::dropEvent(QDropEvent *event) -{ - emit Dropped(event); - - if (!event->isAccepted()) { - super::dropEvent(event); - } + return super::eventFilter(o, e); } void ViewerDisplayWidget::OnPaint() @@ -792,11 +653,17 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) QTransform gizmo_transform = GenerateDisplayTransform(); ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); + + text_edit->setWindowFlags(text_edit->windowFlags() | Qt::Tool | Qt::FramelessWindowHint); + text_edit->setAttribute(Qt::WA_NoSystemBackground); + text_edit->setAttribute(Qt::WA_TranslucentBackground); + 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()); + QRect transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect().toRect(); + + text_edit->setGeometry(QRect(mapToGlobal(transformed_geom.topLeft()), mapToGlobal(transformed_geom.bottomRight()))); ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); @@ -843,6 +710,172 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) }); } +bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) +{ + if (IsHandDrag(event)) { + + // Handle hand drag + hand_last_drag_pos_ = event->pos(); + hand_dragging_ = true; + emit HandDragStarted(); + setCursor(Qt::ClosedHandCursor); + + return true; + + } else if (event->button() == Qt::LeftButton) { + + if (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 (gizmos_ + && (gizmo_last_draw_transform_inverted_ = 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(); + gizmo_last_drag_ = gizmo_start_drag_; + current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime())); + + } else { + + // Handle standard drag + emit DragStarted(); + + } + + return true; + + } + + return false; +} + +bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) +{ + // Handle hand dragging + if (hand_dragging_) { + + // Emit movement + emit HandDragMoved(event->x() - hand_last_drag_pos_.x(), + event->y() - hand_last_drag_pos_.y()); + + hand_last_drag_pos_ = event->pos(); + + return true; + + } else if (add_band_) { + + add_band_->setGeometry(QRect(event->pos(), add_band_start_).normalized()); + + return true; + + } else if (current_gizmo_) { + + // Signal movement + if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { + if (!gizmo_drag_started_) { + QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; + + rational gizmo_time = GetGizmoTime(); + NodeTraverser t; + t.SetCacheVideoParams(gizmo_params_); + NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base())); + + draggable->DragStart(row, start.x(), start.y(), gizmo_time); + gizmo_drag_started_ = true; + } + + QPointF v = event->pos() * gizmo_last_draw_transform_inverted_; + switch (draggable->GetDragValueBehavior()) { + case DraggableGizmo::kAbsolute: + // Above value is correct + break; + case DraggableGizmo::kDeltaFromPrevious: + v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_; + gizmo_last_drag_ = event->pos(); + break; + case DraggableGizmo::kDeltaFromStart: + v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; + break; + } + + draggable->DragMove(v.x(), v.y(), event->modifiers()); + + return true; + } + + } + + return false; +} + +bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e) +{ + if (hand_dragging_) { + + // Handle hand drag + emit HandDragEnded(); + hand_dragging_ = false; + UpdateCursor(); + + return true; + + } else if (add_band_) { + + const QRect &band_rect = add_band_->geometry(); + if (band_rect.width() > 1 && band_rect.height() > 1) { + QRectF r = GenerateDisplayTransform().inverted().mapRect(add_band_->geometry()); + emit CreateAddableAt(r); + } + + add_band_->deleteLater(); + add_band_ = nullptr; + + return true; + + } else if (current_gizmo_) { + + // Handle gizmo + if (gizmo_drag_started_) { + MultiUndoCommand *command = new MultiUndoCommand(); + if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { + draggable->DragEnd(command); + } + Core::instance()->undo_stack()->pushIfHasChildren(command); + gizmo_drag_started_ = false; + } + current_gizmo_ = nullptr; + + return true; + + } + + return false; +} + +bool ViewerDisplayWidget::OnMouseDoubleClick(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && gizmos_) { + QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos()); + foreach (NodeGizmo *g, gizmos_->GetGizmos()) { + if (TextGizmo *text = dynamic_cast(g)) { + if (text->GetRect().contains(ptr)) { + OpenTextGizmo(text, event); + return true; + } + } + } + } + + return false; +} + void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) { // Do this no matter what, emits signal to any pixel samplers diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 629a682aa..2b1491f5e 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -130,6 +130,8 @@ public: return &timer_; } + virtual bool eventFilter(QObject *o, QEvent *e) override; + public slots: /** * @brief Set the transformation matrix to draw with @@ -215,30 +217,6 @@ signals: void CreateAddableAt(const QRectF &rect); -protected: - /** - * @brief Override the mouse press event for the DragStarted() signal and gizmos - */ - virtual void mousePressEvent(QMouseEvent* event) override; - - /** - * @brief Override mouse move to signal for the pixel sampler and gizmos - */ - virtual void mouseMoveEvent(QMouseEvent* event) override; - - /** - * @brief Override mouse release event for gizmos - */ - virtual void mouseReleaseEvent(QMouseEvent* event) override; - - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; - - virtual void dragEnterEvent(QDragEnterEvent* event) override; - - virtual void dragLeaveEvent(QDragLeaveEvent* event) override; - - virtual void dropEvent(QDropEvent* event) override; - protected slots: /** * @brief Paint function to display the texture (received in SetTexture()) on screen. @@ -278,6 +256,13 @@ private: void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr); + bool OnMousePress(QMouseEvent *e); + bool OnMouseMove(QMouseEvent *e); + bool OnMouseRelease(QMouseEvent *e); + bool OnMouseDoubleClick(QMouseEvent *e); + + void EmitColorAtCursor(QMouseEvent* e); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ @@ -390,8 +375,6 @@ private: bool queue_starved_; private slots: - void EmitColorAtCursor(QMouseEvent* e); - void UpdateFromQueue(); void TextEditChanged(); From d32a0527087fd6ed9bc88e5eeabe20e19184be5b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 19 Jun 2022 00:06:51 -0700 Subject: [PATCH 14/40] viewertexteditor: null check clone --- app/widget/viewer/viewertexteditor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 76fd94037..7c56eaa50 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -134,7 +134,9 @@ void ViewerTextEditor::paintEvent(QPaintEvent *e) ctx.selections.append(selection); } - transparent_clone_->documentLayout()->draw(&p, ctx); + if (transparent_clone_) { + transparent_clone_->documentLayout()->draw(&p, ctx); + } } void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment) From e69436a27d23cd70846e23d1d5d3370b18e34476 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 19 Jun 2022 09:35:28 -0700 Subject: [PATCH 15/40] viewer: null check shaders before destroy --- app/widget/viewer/viewerdisplay.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 7d6d660c9..1d03c75fb 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -503,10 +503,14 @@ void ViewerDisplayWidget::OnPaint() void ViewerDisplayWidget::OnDestroy() { - renderer()->DestroyNativeShader(deinterlace_shader_); - deinterlace_shader_.clear(); - renderer()->DestroyNativeShader(blank_shader_); - blank_shader_.clear(); + if (!deinterlace_shader_.isNull()) { + renderer()->DestroyNativeShader(deinterlace_shader_); + deinterlace_shader_.clear(); + } + if (!blank_shader_.isNull()) { + renderer()->DestroyNativeShader(blank_shader_); + blank_shader_.clear(); + } super::OnDestroy(); From 7bfe5f563e48af2c6d28fd209981a6af14cb575b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 19 Jun 2022 13:13:25 -0700 Subject: [PATCH 16/40] viewer: use popup for text editor --- app/widget/viewer/viewerdisplay.cpp | 102 +++++++++++++++++-------- app/widget/viewer/viewertexteditor.cpp | 49 ++---------- app/widget/viewer/viewertexteditor.h | 6 +- 3 files changed, 77 insertions(+), 80 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1d03c75fb..1efaff97f 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -248,10 +248,15 @@ bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e) switch (e->type()) { case QEvent::MouseButtonPress: - if (OnMousePress(static_cast(e))) { - return true; + { + QMouseEvent *mouse = static_cast(e); + if (!(mouse->flags() & Qt::MouseEventCreatedDoubleClick)) { + if (OnMousePress(mouse)) { + return true; + } } break; + } case QEvent::MouseMove: EmitColorAtCursor(static_cast(e)); if (OnMouseMove(static_cast(e))) { @@ -656,61 +661,94 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) { QTransform gizmo_transform = GenerateDisplayTransform(); - ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); - - text_edit->setWindowFlags(text_edit->windowFlags() | Qt::Tool | Qt::FramelessWindowHint); - text_edit->setAttribute(Qt::WA_NoSystemBackground); - text_edit->setAttribute(Qt::WA_TranslucentBackground); + // Create popup container for text and toolbar + auto popup = new QWidget(this); + popup->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); + popup->setAttribute(Qt::WA_DeleteOnClose); + popup->setAttribute(Qt::WA_TranslucentBackground); + // Create text editor + ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), popup); Html::HtmlToDoc(text_edit->document(), text->GetHtml()); text_edit->setProperty("gizmo", reinterpret_cast(text)); + connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); - QRect transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect().toRect(); + // Get on screen text rect (this will be the text editor's global geometry) + QRect global_text_area = gizmo_transform.map(text->GetRect()).boundingRect().toRect(); + global_text_area = QRect(mapToGlobal(global_text_area.topLeft()), mapToGlobal(global_text_area.bottomRight())); - text_edit->setGeometry(QRect(mapToGlobal(transformed_geom.topLeft()), mapToGlobal(transformed_geom.bottomRight()))); + QRect global_popup_area = global_text_area; - ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); + // Create toolbar + ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(popup); + text_edit->ConnectToolBar(toolbar); - QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height())); + // Work out which corner of the text editor to anchor the toolbar to based on screen limitations + bool top = true; + bool left = true; 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()); + // Look for screen that contains text area + if (screen->geometry().contains(global_text_area)) { + if (global_text_area.left() + toolbar->width() > screen->geometry().right()) { + left = false; + } + if (global_text_area.top() - toolbar->height() < screen->geometry().top()) { + top = false; } break; } } - toolbar->move(pos); - toolbar->show(); - text_edit->show(); - connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); + QPoint toolbar_pos; - text_edit->ConnectToolBar(toolbar); + if (top) { + global_popup_area.adjust(0, -toolbar->height(), 0, 0); + toolbar_pos.setY(0); + } else { + global_popup_area.adjust(0, 0, 0, toolbar->height()); + toolbar_pos.setY(global_text_area.height()); + } - QPoint text_edit_pos; + if (toolbar->width() > global_popup_area.width()) { + int diff = toolbar->width() - global_popup_area.width(); + if (left) { + global_popup_area.adjust(0, 0, diff, 0); + } else { + global_popup_area.adjust(-diff, 0, 0, 0); + } + toolbar_pos.setX(0); + } else { + if (left) { + toolbar_pos.setX(0); + } else { + toolbar_pos.setX(global_popup_area.width() - toolbar->width()); + } + } + + toolbar->move(toolbar_pos); + + popup->setGeometry(global_popup_area); + + text_edit->setGeometry(QRect(text_edit->mapFromGlobal(global_text_area.topLeft()), text_edit->mapFromGlobal(global_text_area.bottomRight()))); + + popup->show(); + + // Store click pos from event so we can use it later to set the initial text cursor position + QPoint click_pos; if (event) { - text_edit_pos = text_edit->mapFrom(this, event->pos()); + click_pos = event->globalPos(); } // Ensure text edit is actually focused rather than the toolbar - connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [this, text_edit, text_edit_pos]{ + connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [text_edit, click_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)); + if (!click_pos.isNull()) { + text_edit->setTextCursor(text_edit->cursorForPosition(text_edit->mapFromGlobal(click_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); }); } diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 7c56eaa50..6a6c3227a 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -61,7 +61,6 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : dpi_force_.setDotsPerMeterY(dpm); document()->documentLayout()->setPaintDevice(&dpi_force_); - connect(qApp, &QApplication::focusChanged, this, &ViewerTextEditor::FocusChanged); connect(this, &QTextEdit::currentCharFormatChanged, this, &ViewerTextEditor::FormatChanged); connect(document(), &QTextDocument::contentsChanged, this, &ViewerTextEditor::DocumentChanged, Qt::QueuedConnection); @@ -70,8 +69,6 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar) { - connect(this, &ViewerTextEditor::destroyed, toolbar, &ViewerTextEditorToolBar::deleteLater); - connect(toolbar, &ViewerTextEditorToolBar::FamilyChanged, this, &ViewerTextEditor::SetFamily); connect(toolbar, &ViewerTextEditorToolBar::SizeChanged, this, &ViewerTextEditor::setFontPointSize); connect(toolbar, &ViewerTextEditorToolBar::StyleChanged, this, &ViewerTextEditor::SetStyle); @@ -94,15 +91,6 @@ void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar) toolbars_.append(toolbar); } -void ViewerTextEditor::keyPressEvent(QKeyEvent *event) -{ - super::keyPressEvent(event); - - if (event->key() == Qt::Key_Escape) { - deleteLater(); - } -} - void ViewerTextEditor::paintEvent(QPaintEvent *e) { QPainter p(this->viewport()); @@ -178,34 +166,6 @@ void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTe toolbar->SetLineHeight(b.lineHeight() == 0.0 ? 100 : b.lineHeight()); } -void ViewerTextEditor::FocusChanged(QWidget *old, QWidget *now) -{ - if (!listen_to_focus_events_) { - return; - } - - QWidget *test = now; - - if (!test) { - // Ignore null focuses because that could be one of the toolbar widgets simply losing focus - // and that would be undesirable to close the text editor from - return; - } - - while (test) { - if (test == this - || dynamic_cast(test) - || dynamic_cast(test)) { - return; - } - - test = test->parentWidget(); - } - - // If we didn't return in the loop, the user must have focused on something else - deleteLater(); -} - void ViewerTextEditor::FormatChanged(const QTextCharFormat &f) { if (!block_update_toolbar_signal_) { @@ -325,8 +285,9 @@ void ViewerTextEditor::DocumentChanged() } ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) : - QWidget(parent, Qt::Tool | Qt::FramelessWindowHint), - painted_(false) + QWidget(parent), + painted_(false), + drag_enabled_(false) { QVBoxLayout *outer_layout = new QVBoxLayout(this); outer_layout->setSpacing(0); @@ -511,7 +472,7 @@ void ViewerTextEditorToolBar::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); - if (event->button() == Qt::LeftButton) { + if (event->button() == Qt::LeftButton && drag_enabled_) { drag_anchor_ = event->pos(); } } @@ -520,7 +481,7 @@ void ViewerTextEditorToolBar::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); - if (event->buttons() & Qt::LeftButton) { + if ((event->buttons() & Qt::LeftButton) && drag_enabled_) { this->move(mapToParent(QPoint(event->pos() - drag_anchor_))); } } diff --git a/app/widget/viewer/viewertexteditor.h b/app/widget/viewer/viewertexteditor.h index 8df6a5285..2731cb710 100644 --- a/app/widget/viewer/viewertexteditor.h +++ b/app/widget/viewer/viewertexteditor.h @@ -126,6 +126,8 @@ private: bool painted_; + bool drag_enabled_; + private slots: void UpdateFontStyleList(const QString &family); @@ -144,8 +146,6 @@ public: void SetListenToFocusEvents(bool e) { listen_to_focus_events_ = e; } protected: - virtual void keyPressEvent(QKeyEvent *event) override; - virtual void paintEvent(QPaintEvent *event) override; private: @@ -166,8 +166,6 @@ private: bool listen_to_focus_events_; private slots: - void FocusChanged(QWidget *old, QWidget *now); - void FormatChanged(const QTextCharFormat &f); void SetFamily(const QString &s); From 52db848918064c2b78f029d3cea97bb9ee02b75a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:26:16 -0700 Subject: [PATCH 17/40] ui: draw in/out semi translucent Fixes #1957 --- app/widget/timeruler/seekablewidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 141a28b70..68f255bc7 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -331,7 +331,9 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) workarea_right = qMin(qreal(lim_right), TimeToScene(GetTimelinePoints()->workarea()->out())); } - p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight()); + QColor translucent_highlight = palette().highlight().color(); + translucent_highlight.setAlpha(96); + p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), translucent_highlight); } // Draw markers From da0b191254baa816e04c07c52a3ac0a0a9214ba3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:07:12 -0700 Subject: [PATCH 18/40] crashhandler: show error code --- app/crashhandler/crashhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crashhandler/crashhandler.cpp b/app/crashhandler/crashhandler.cpp index eac036c27..ac1173123 100644 --- a/app/crashhandler/crashhandler.cpp +++ b/app/crashhandler/crashhandler.cpp @@ -153,7 +153,7 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Upload Failed")); - b.setText(tr("Failed to send error report. Please try again later.")); + b.setText(tr("Failed to send error report (%1). Please try again later.").arg(QString::number(reply.error()))); b.addButton(QMessageBox::Ok); b.exec(); From bc49cfeeded10eb4a73bdfac20ad586ac836045c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:10:58 -0700 Subject: [PATCH 19/40] crashhandler: use correct c++ thing --- app/crashhandler/crashhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crashhandler/crashhandler.cpp b/app/crashhandler/crashhandler.cpp index ac1173123..a8d9368bb 100644 --- a/app/crashhandler/crashhandler.cpp +++ b/app/crashhandler/crashhandler.cpp @@ -153,7 +153,7 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Upload Failed")); - b.setText(tr("Failed to send error report (%1). Please try again later.").arg(QString::number(reply.error()))); + b.setText(tr("Failed to send error report (%1). Please try again later.").arg(QString::number(reply->error()))); b.addButton(QMessageBox::Ok); b.exec(); From 0fc6ab149f3ff9a49ff4e2a1711f00a9f36ec68d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:26:19 -0700 Subject: [PATCH 20/40] timeruler: fixed scroll regression Fixes #1960 --- app/widget/timeruler/seekablewidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 68f255bc7..45cfb9717 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -364,8 +364,11 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) { int half_width = playhead_width_ / 2; - if (x + half_width < 0 || x - half_width > width()) { - return; + { + int test = x - this->GetScroll(); + if (test + half_width < 0 || test - half_width > width()) { + return; + } } p->setRenderHint(QPainter::Antialiasing); From 75bc2c2e67a908eaaa7e4179e2b08cc26a2c136d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:42:05 -0700 Subject: [PATCH 21/40] projectexplorer: implement replace footage function --- app/widget/projectexplorer/projectexplorer.cpp | 16 ++++++++++++++++ app/widget/projectexplorer/projectexplorer.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 2236d4b84..8236dfe55 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "task/taskmanager.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "window/mainwindow/mainwindow.h" #include "window/mainwindow/mainwindowundo.h" #include "widget/nodeview/nodeviewundo.h" @@ -392,6 +394,9 @@ void ProjectExplorer::ShowContextMenu() QAction* reveal_action = menu.addAction(reveal_text); connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage); + QAction *replace_action = menu.addAction(tr("Replace Footage")); + connect(replace_action, &QAction::triggered, this, &ProjectExplorer::ReplaceSelectedFootage); + } menu.addSeparator(); @@ -497,6 +502,17 @@ void ProjectExplorer::RevealSelectedFootage() #endif } +void ProjectExplorer::ReplaceSelectedFootage() +{ + Footage* footage = static_cast(context_menu_items_.first()); + + QString file = QFileDialog::getOpenFileName(this, tr("Replace Footage")); + if (!file.isEmpty()) { + auto c = new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(footage, Footage::kFilenameInput)), file); + Core::instance()->undo_stack()->push(c); + } +} + void ProjectExplorer::OpenContextMenuItemInNewTab() { Core::instance()->main_window()->FolderOpen(project(), static_cast(context_menu_items_.first()), false); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 30200a757..eb31a2acc 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -185,6 +185,8 @@ private slots: void RevealSelectedFootage(); + void ReplaceSelectedFootage(); + void OpenContextMenuItemInNewTab(); void OpenContextMenuItemInNewWindow(); From a5092494b84923167ffcac57e52fb841c944d4b5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:42:29 -0700 Subject: [PATCH 22/40] core: if startup project is cancelled on footage relink, open new project --- app/core.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/core.cpp b/app/core.cpp index f6f18e2fe..c9b701ead 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -522,6 +522,9 @@ bool Core::AddOpenProjectFromTask(Task *task) return true; } else { delete project; + if (open_projects_.empty()) { + CreateNewProject(); + } } } From 3a7dfdb6811872ce0abc353248fb1dd55f496622 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:23:44 -0700 Subject: [PATCH 23/40] viewer: allow showing waveform and viewer simultaneously Fixes #1959 --- app/widget/viewer/viewer.cpp | 91 +++++++++++++++++++----------------- app/widget/viewer/viewer.h | 15 ++++-- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index b769a0925..1387874ad 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -72,19 +72,17 @@ ViewerWidget::ViewerWidget(QWidget *parent) : record_armed_(false), recording_(false), first_requeue_watcher_(nullptr), - enable_audio_scrubbing_(true) + enable_audio_scrubbing_(true), + waveform_mode_(kWFAutomatic) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); - // Set up stacked widget to allow switching away from the viewer widget - stack_ = new QStackedWidget(); - layout->addWidget(stack_); - // Create main OpenGL-based view and sizer sizer_ = new ViewerSizer(); - stack_->addWidget(sizer_); + sizer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(sizer_); display_widget_ = new ViewerDisplayWidget(); display_widget_->setAcceptDrops(true); @@ -114,7 +112,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : waveform_view_ = new AudioWaveformView(); ConnectTimelineView(waveform_view_, true); PassWheelEventsToScrollBar(waveform_view_); - stack_->addWidget(waveform_view_); + layout->addWidget(waveform_view_); // Create time ruler layout->addWidget(ruler()); @@ -209,7 +207,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); - connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateStack); + connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); VideoParams vp = n->GetVideoParams(); @@ -230,7 +228,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) dw->ConnectColorManager(color_manager); } - UpdateStack(); + UpdateWaveformViewFromMode(); waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache()); waveform_view_->ConnectTimelinePoints(GetConnectedNode()->GetTimelinePoints()); @@ -253,7 +251,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); - disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateStack); + disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); CloseAudioProcessor(); @@ -272,7 +270,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) waveform_view_->ConnectTimelinePoints(nullptr); // Queue an UpdateStack so that when it runs, the viewer node will be fully disconnected - QMetaObject::invokeMethod(this, &ViewerWidget::UpdateStack, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ViewerWidget::UpdateWaveformViewFromMode, Qt::QueuedConnection); SetGizmos(nullptr); } @@ -543,6 +541,22 @@ void ViewerWidget::CloseAudioProcessor() audio_processor_.Close(); } +void ViewerWidget::SetWaveformMode(WaveformMode wf) +{ + waveform_mode_ = wf; + UpdateWaveformViewFromMode(); +} + +void ViewerWidget::UpdateWaveformViewFromMode() +{ + bool prefer_waveform = ShouldForceWaveform(); + + sizer_->setVisible(waveform_mode_ == kWFViewerAndWaveform || waveform_mode_ == kWFViewerOnly || (waveform_mode_ == kWFAutomatic && !prefer_waveform)); + waveform_view_->setVisible(waveform_mode_ == kWFViewerAndWaveform || waveform_mode_ == kWFWaveformOnly || (waveform_mode_ == kWFAutomatic && prefer_waveform)); + + waveform_view_->setSizePolicy(QSizePolicy::Expanding, waveform_mode_ == kWFViewerAndWaveform ? QSizePolicy::Maximum : QSizePolicy::Expanding); +} + void ViewerWidget::QueueNextAudioBuffer() { rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_); @@ -1014,28 +1028,6 @@ int ViewerWidget::DeterminePlaybackQueueSize() return qMin(max_frames, remaining_frames); } -void ViewerWidget::UpdateStack() -{ - rational new_tb; - - if (ShouldForceWaveform()) { - // If we have a node AND video is disconnected AND audio is connected, show waveform view - stack_->setCurrentWidget(waveform_view_); - //new_tb = GetConnectedNode()->audio_params().time_base(); - } else { - // Otherwise show regular display - stack_->setCurrentWidget(sizer_); - - /*if (GetConnectedNode()) { - new_tb = GetConnectedNode()->video_params().time_base(); - }*/ - } - - /*if (new_tb != timebase()) { - SetTimebase(new_tb); - }*/ -} - void ViewerWidget::ContextMenuSetFullScreen(QAction *action) { SetFullScreen(QGuiApplication::screens().at(action->data().toInt())); @@ -1259,11 +1251,26 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) } { - QAction* show_waveform_action = menu.addAction(tr("Show Audio Waveform")); - show_waveform_action->setCheckable(true); - show_waveform_action->setChecked(stack_->currentWidget() == waveform_view_); - show_waveform_action->setEnabled(!ShouldForceWaveform()); - connect(show_waveform_action, &QAction::triggered, this, &ViewerWidget::ManualSwitchToWaveform); + auto waveform_menu = new Menu(tr("Audio Waveform"), &menu); + menu.addMenu(waveform_menu); + + auto auto_showhide = waveform_menu->addAction(tr("Automatically Show/Hide")); + auto show_waveform = waveform_menu->addAction(tr("Show Waveform Only")); + auto show_both = waveform_menu->addAction(tr("Show Both Viewer And Waveform")); + + auto_showhide->setCheckable(true); + show_waveform->setCheckable(true); + show_both->setCheckable(true); + + auto_showhide->setData(kWFAutomatic); + show_waveform->setData(kWFWaveformOnly); + show_both->setData(kWFViewerAndWaveform); + + auto_showhide->setChecked(waveform_mode_ == kWFAutomatic); + show_waveform->setChecked(waveform_mode_ == kWFWaveformOnly); + show_both->setChecked(waveform_mode_ == kWFViewerAndWaveform); + + connect(waveform_menu, &Menu::triggered, this, &ViewerWidget::UpdateWaveformModeFromMenu); } { @@ -1579,13 +1586,9 @@ void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range) } } -void ViewerWidget::ManualSwitchToWaveform(bool e) +void ViewerWidget::UpdateWaveformModeFromMenu(QAction *a) { - if (e) { - stack_->setCurrentWidget(waveform_view_); - } else { - stack_->setCurrentWidget(sizer_); - } + SetWaveformMode(static_cast(a->data().toInt())); } void ViewerWidget::DragEntered(QDragEnterEvent* event) diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4a879c367..86b8969c4 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -51,6 +51,13 @@ class ViewerWidget : public TimeBasedWidget { Q_OBJECT public: + enum WaveformMode { + kWFAutomatic, + kWFViewerOnly, + kWFWaveformOnly, + kWFViewerAndWaveform + }; + ViewerWidget(QWidget* parent = nullptr); virtual ~ViewerWidget() override; @@ -223,7 +230,7 @@ private: void CloseAudioProcessor(); - QStackedWidget* stack_; + void SetWaveformMode(WaveformMode wf); ViewerSizer* sizer_; @@ -284,6 +291,8 @@ private: bool enable_audio_scrubbing_; + WaveformMode waveform_mode_; + private slots: void PlaybackTimerUpdate(); @@ -299,7 +308,7 @@ private slots: void SetZoomFromMenu(QAction* action); - void UpdateStack(); + void UpdateWaveformViewFromMode(); void ContextMenuSetFullScreen(QAction* action); @@ -317,7 +326,7 @@ private slots: void ViewerInvalidatedVideoRange(const olive::TimeRange &range); - void ManualSwitchToWaveform(bool e); + void UpdateWaveformModeFromMenu(QAction *a); void DragEntered(QDragEnterEvent* event); From 080227616d4db8c0ae8e169040ebed39790ac3e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:06:24 -0700 Subject: [PATCH 24/40] viewer: fix bug that overqueued frames --- app/widget/viewer/viewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 1387874ad..801512e60 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1504,7 +1504,7 @@ void ViewerWidget::PlaybackTimerUpdate() } if (IsPlaying()) { - while (queue_watchers_.size() < DeterminePlaybackQueueSize()) { + while ((int(display_widget_->queue()->size()) + queue_watchers_.size()) < DeterminePlaybackQueueSize()) { if (!RequestNextFrameForQueue()) { // Prevent infinite loop break; From de31f3351a11e2eba170fdb206512ff707451afd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:35:31 -0700 Subject: [PATCH 25/40] mainwindow: use primary screen rather than desktop widget --- app/window/mainwindow/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 176575e40..f1b238f6e 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef Q_OS_LINUX #include @@ -43,7 +44,9 @@ MainWindow::MainWindow(QWidget *parent) : // window beforehand works around that issue and we just set it to whatever size is available. // * On Linux, it seems the window starts off at a vastly different size and then maximizes // which throws off the proportions and makes the resulting layout wonky. - resize(qApp->desktop()->availableGeometry(this).size()); + if (!qApp->screens().empty()) { + resize(qApp->screens().at(0)->availableSize()); + } #ifdef Q_OS_WINDOWS // Set up taskbar button progress bar (used for some modal tasks like exporting) From 3f8001b146b6d5370282403faeb8a8e1d2abe606 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 14:08:29 -0700 Subject: [PATCH 26/40] render: use single base thread for rendering --- app/codec/ffmpeg/ffmpegdecoder.cpp | 8 ++++++-- app/render/rendermanager.cpp | 24 ++++++++++++++++++------ app/render/rendermanager.h | 10 +++++++++- app/task/render/render.cpp | 4 ++++ app/widget/viewer/viewer.cpp | 8 +++++++- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index a2ebad125..bfd53c4f0 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -838,7 +838,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt * } else { // Cut down to thread count - 1 before we acquire a new frame - if (cached_frames_.size() == size_t(MaximumQueueSize())) { + if (cached_frames_.size() > size_t(MaximumQueueSize())) { RemoveFirstFrame(); } @@ -1041,7 +1041,11 @@ void FFmpegDecoder::RemoveFirstFrame() int FFmpegDecoder::MaximumQueueSize() { - return QThread::idealThreadCount(); + // Fairly arbitrary size. This used to need to be the number of current threads to ensure any + // thread that arrived would have its frame available, but if we only have one render thread, + // that's no longer a concern. Now, this value could technically be 1, but some memory cache + // may be useful for reversing. This value may be tweaked over time. + return 2; } FFmpegDecoder::Instance::Instance() : diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 80ccdaf70..6281a380b 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -38,8 +38,9 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; RenderManager::RenderManager(QObject *parent) : - ThreadPool(0, parent), - backend_(kOpenGL) + ThreadPool(1, parent), + backend_(kOpenGL), + aggressive_gc_(0) { Renderer* graphics_renderer = nullptr; @@ -60,10 +61,10 @@ RenderManager::RenderManager(QObject *parent) : decoder_cache_ = nullptr; } - QTimer *decoder_clear_timer = new QTimer(this); - decoder_clear_timer->setInterval(kDecoderMaximumInactivity); - connect(decoder_clear_timer, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); - decoder_clear_timer->start(); + decoder_clear_timer_ = new QTimer(this); + decoder_clear_timer_->setInterval(kDecoderMaximumInactivity); + connect(decoder_clear_timer_, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); + decoder_clear_timer_->start(); } RenderManager::~RenderManager() @@ -162,6 +163,17 @@ void RenderManager::RunTicket(RenderTicketPtr ticket) const RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); } +void RenderManager::SetAggressiveGarbageCollection(bool enabled) +{ + aggressive_gc_ += enabled ? 1 : -1; + + if (aggressive_gc_ > 0) { + decoder_clear_timer_->setInterval(kDecoderMaximumInactivityAggressive); + } else { + decoder_clear_timer_->setInterval(kDecoderMaximumInactivity); + } +} + void RenderManager::ClearOldDecoders() { QMutexLocker locker(decoder_cache_->mutex()); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 24d29e34a..6452f4f0b 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -113,6 +113,9 @@ public: return QThread::idealThreadCount(); } +public slots: + void SetAggressiveGarbageCollection(bool enabled); + signals: private: @@ -130,7 +133,12 @@ private: ShaderCache* shader_cache_; - static constexpr auto kDecoderMaximumInactivity = 10000; + static constexpr auto kDecoderMaximumInactivityAggressive = 1000; + static constexpr auto kDecoderMaximumInactivity = 5000; + + int aggressive_gc_; + + QTimer *decoder_clear_timer_; private slots: void ClearOldDecoders(); diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 2968f6d5a..23fa2e387 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -44,6 +44,8 @@ bool RenderTask::Render(ColorManager* manager, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output) { + QMetaObject::invokeMethod(RenderManager::instance(), "SetAggressiveGarbageCollection", Q_ARG(bool, true)); + // Run watchers in another thread so they can accept signals even while this thread is blocked QThread watcher_thread; watcher_thread.start(); @@ -236,6 +238,8 @@ bool RenderTask::Render(ColorManager* manager, watcher_thread.quit(); watcher_thread.wait(); + QMetaObject::invokeMethod(RenderManager::instance(), "SetAggressiveGarbageCollection", Q_ARG(bool, false)); + return result; } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 801512e60..e0f01c404 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -765,6 +765,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) viewer->auto_cacher_.SetAudioPaused(true); } + RenderManager::instance()->SetAggressiveGarbageCollection(true); + // Disarm recording if armed if (record_armed_) { DisarmRecording(); @@ -861,6 +863,8 @@ void ViewerWidget::PauseInternal() } UpdateTextureFromNode(); + + RenderManager::instance()->SetAggressiveGarbageCollection(false); } prequeuing_video_ = false; @@ -1108,7 +1112,9 @@ void ViewerWidget::RendererGeneratedFrameForQueue() prequeuing_video_ = false; FinishPlayPreprocess(); } else { - RequestNextFrameForQueue(); + // This call was mostly necessary to keep the threads busy between prequeue and playback. + // If we only have a single render thread, it's no longer necessary. + //RequestNextFrameForQueue(); } } } From 99bbb163eff64b56ef949eb18112fc7091057b4e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Jun 2022 14:10:11 -0700 Subject: [PATCH 27/40] render: remove deprecated code --- app/render/previewautocacher.cpp | 4 ++-- app/render/rendermanager.h | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 9ea8439cb..e27dc2cf7 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -574,8 +574,8 @@ void PreviewAutoCacher::TryRender() single_frame_render_ = nullptr; } - // Ensure we are running tasks if we have any - const int max_tasks = RenderManager::GetNumberOfIdealConcurrentJobs(); + // Completely arbitrary number. I don't know what's optimal for this yet. + const int max_tasks = 4; // Handle video tasks rational t; diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 6452f4f0b..c79c031fc 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -108,11 +108,6 @@ public: return backend_; } - static int GetNumberOfIdealConcurrentJobs() - { - return QThread::idealThreadCount(); - } - public slots: void SetAggressiveGarbageCollection(bool enabled); From eb046fcd3ea5ec182432b81aa06a1ed1b246c04f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:54:14 -0700 Subject: [PATCH 28/40] viewernode: use convenience functions --- app/node/output/viewer/viewer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 50caee988..a9476a101 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -331,7 +331,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const case Track::kVideo: if (IsInputConnected(kTextureInput)) { NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); - rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); if (!r.isNaN()) { return r; } @@ -340,7 +340,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const case Track::kAudio: if (IsInputConnected(kSamplesInput)) { NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); - rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value();; + rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); if (!r.isNaN()) { return r; } From 15d6f407eb62f4298be7b9da0f6f4ee79549cf46 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:55:30 -0700 Subject: [PATCH 29/40] viewer: implement playback resolution option --- app/render/videoparams.cpp | 9 ++++ app/render/videoparams.h | 2 + .../standardcombos/videodividercombobox.h | 10 +---- app/widget/viewer/viewer.cpp | 44 ++++++++++++------- app/widget/viewer/viewer.h | 2 + 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index c40ff3684..c9db6f408 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -200,6 +200,15 @@ int VideoParams::GetBytesPerPixel(VideoParams::Format format, int channels) return GetBytesPerChannel(format) * channels; } +QString VideoParams::GetNameForDivider(int div) +{ + if (div == 1) { + return QCoreApplication::translate("VideoParams", "Full"); + } else { + return QCoreApplication::translate("VideoParams", "1/%1").arg(div); + } +} + bool VideoParams::FormatIsFloat(VideoParams::Format format) { switch (format) { diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 564985dc4..f3cedd28b 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -231,6 +231,8 @@ public: return GetBufferSize(width_, height_, format_, channel_count_); } + static QString GetNameForDivider(int div); + static bool FormatIsFloat(Format format); static QString GetFormatName(Format format); diff --git a/app/widget/standardcombos/videodividercombobox.h b/app/widget/standardcombos/videodividercombobox.h index 05c068458..caaeb74da 100644 --- a/app/widget/standardcombos/videodividercombobox.h +++ b/app/widget/standardcombos/videodividercombobox.h @@ -35,15 +35,7 @@ public: QComboBox(parent) { foreach (int d, VideoParams::kSupportedDividers) { - QString name; - - if (d == 1) { - name = tr("Full"); - } else { - name = tr("1/%1").arg(d); - } - - this->addItem(name, d); + this->addItem(VideoParams::GetNameForDivider(d), d); } } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index e0f01c404..dedca355c 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -45,6 +45,7 @@ #include "viewerpreventsleep.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/timelinewidget/tool/add.h" #include "widget/timeruler/timeruler.h" @@ -205,6 +206,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); connect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot); connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); + connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection); connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); @@ -249,6 +251,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); disconnect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot); disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); + disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateTextureFromNode); disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); @@ -1037,6 +1040,17 @@ void ViewerWidget::ContextMenuSetFullScreen(QAction *action) SetFullScreen(QGuiApplication::screens().at(action->data().toInt())); } +void ViewerWidget::ContextMenuSetPlaybackRes(QAction *action) +{ + int div = action->data().toInt(); + + auto vp = GetConnectedNode()->GetVideoParams(); + vp.set_divider(div); + + auto c = new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(GetConnectedNode(), ViewerOutput::kVideoParamsInput, 0)), QVariant::fromValue(vp)); + Core::instance()->undo_stack()->push(c); +} + void ViewerWidget::ContextMenuDisableSafeMargins() { context_menu_widget_->SetSafeMargins(ViewerSafeMarginInfo(false)); @@ -1194,6 +1208,18 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) connect(full_screen_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuSetFullScreen); } + { + // Playback Resolution Menu + Menu *playback_res_menu = new Menu(tr("Playback Resolution"), &menu); + menu.addMenu(playback_res_menu); + + for (int d : VideoParams::kSupportedDividers) { + playback_res_menu->AddActionWithData(VideoParams::GetNameForDivider(d), d, GetConnectedNode()->GetVideoParams().divider()); + } + + connect(playback_res_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuSetPlaybackRes); + } + { // Deinterlace Option if (GetConnectedNode()->GetVideoParams().interlacing() != VideoParams::kInterlaceNone) { @@ -1260,21 +1286,9 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) auto waveform_menu = new Menu(tr("Audio Waveform"), &menu); menu.addMenu(waveform_menu); - auto auto_showhide = waveform_menu->addAction(tr("Automatically Show/Hide")); - auto show_waveform = waveform_menu->addAction(tr("Show Waveform Only")); - auto show_both = waveform_menu->addAction(tr("Show Both Viewer And Waveform")); - - auto_showhide->setCheckable(true); - show_waveform->setCheckable(true); - show_both->setCheckable(true); - - auto_showhide->setData(kWFAutomatic); - show_waveform->setData(kWFWaveformOnly); - show_both->setData(kWFViewerAndWaveform); - - auto_showhide->setChecked(waveform_mode_ == kWFAutomatic); - show_waveform->setChecked(waveform_mode_ == kWFWaveformOnly); - show_both->setChecked(waveform_mode_ == kWFViewerAndWaveform); + waveform_menu->AddActionWithData(tr("Automatically Show/Hide"), kWFAutomatic, waveform_mode_); + waveform_menu->AddActionWithData(tr("Show Waveform Only"), kWFWaveformOnly, waveform_mode_); + waveform_menu->AddActionWithData(tr("Show Both Viewer And Waveform"), kWFViewerAndWaveform, waveform_mode_); connect(waveform_menu, &Menu::triggered, this, &ViewerWidget::UpdateWaveformModeFromMenu); } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 86b8969c4..ebb5c7ba5 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -312,6 +312,8 @@ private slots: void ContextMenuSetFullScreen(QAction* action); + void ContextMenuSetPlaybackRes(QAction* action); + void ContextMenuDisableSafeMargins(); void ContextMenuSetSafeMargins(); From ec215f7de6cfcc8d4adaeeb856c56d71e3809119 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 24 Jun 2022 09:59:28 -0700 Subject: [PATCH 30/40] renderer: attach renderer to single thread --- app/render/rendermanager.cpp | 129 ++++++++++++++++++++++++++++------- app/render/rendermanager.h | 39 ++++++++++- 2 files changed, 143 insertions(+), 25 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 6281a380b..2c4be2b1b 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -38,21 +38,11 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; RenderManager::RenderManager(QObject *parent) : - ThreadPool(1, parent), backend_(kOpenGL), aggressive_gc_(0) { - Renderer* graphics_renderer = nullptr; - if (backend_ == kOpenGL) { - graphics_renderer = new OpenGLRenderer(); - } - - if (graphics_renderer) { - context_ = new RendererThreadWrapper(graphics_renderer, this); - context_->Init(); - context_->PostInit(); - + context_ = new OpenGLRenderer(); decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); } else { @@ -61,6 +51,14 @@ RenderManager::RenderManager(QObject *parent) : decoder_cache_ = nullptr; } + if (context_) { + video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this); + audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); + + video_thread_->start(QThread::IdlePriority); + audio_thread_->start(QThread::IdlePriority); + } + decoder_clear_timer_ = new QTimer(this); decoder_clear_timer_->setInterval(kDecoderMaximumInactivity); connect(decoder_clear_timer_, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); @@ -73,9 +71,14 @@ RenderManager::~RenderManager() delete shader_cache_; delete decoder_cache_; - context_->Destroy(); + video_thread_->quit(); + video_thread_->wait(); + context_->PostDestroy(); delete context_; + + audio_thread_->quit(); + audio_thread_->wait(); } } @@ -128,7 +131,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid())); } - AddTicket(ticket, priority); + video_thread_->AddTicket(ticket); return ticket; } @@ -145,22 +148,20 @@ RenderTicketPtr RenderManager::RenderAudio(Node *node, const TimeRange &r, const ticket->setProperty("enablewaveforms", generate_waveforms); ticket->setProperty("aparam", QVariant::fromValue(params)); - AddTicket(ticket, priority); + audio_thread_->AddTicket(ticket); return ticket; } -void RenderManager::RunTicket(RenderTicketPtr ticket) const +bool RenderManager::RemoveTicket(RenderTicketPtr ticket) { - // Setup the ticket for ::Process - ticket->Start(); - - if (ticket->IsCancelled()) { - ticket->Finish(); - return; + if (video_thread_->RemoveTicket(ticket)) { + return true; + } else if (audio_thread_->RemoveTicket(ticket)) { + return true; + } else { + return false; } - - RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); } void RenderManager::SetAggressiveGarbageCollection(bool enabled) @@ -192,4 +193,86 @@ void RenderManager::ClearOldDecoders() } } +RenderThread::RenderThread(Renderer *renderer, DecoderCache *decoder_cache, ShaderCache *shader_cache, QObject *parent) : + QThread(parent), + cancelled_(false), + context_(renderer), + decoder_cache_(decoder_cache), + shader_cache_(shader_cache) +{ + if (context_) { + context_->Init(); + context_->moveToThread(this); + } +} + +void RenderThread::AddTicket(RenderTicketPtr ticket) +{ + QMutexLocker locker(&mutex_); + queue_.push_back(ticket); + wait_.wakeOne(); +} + +bool RenderThread::RemoveTicket(RenderTicketPtr ticket) +{ + QMutexLocker locker(&mutex_); + + auto it = std::find(queue_.begin(), queue_.end(), ticket); + if (it == queue_.end()) { + return false; + } + + queue_.erase(it); + return true; +} + +void RenderThread::quit() +{ + QMutexLocker locker(&mutex_); + cancelled_ = true; + wait_.wakeOne(); +} + +void RenderThread::run() +{ + if (context_) { + context_->PostInit(); + } + + QMutexLocker locker(&mutex_); + + while (!cancelled_) { + if (queue_.empty()) { + wait_.wait(&mutex_); + } + + if (cancelled_) { + break; + } + + if (!queue_.empty()) { + RenderTicketPtr ticket = queue_.front(); + queue_.pop_front(); + + locker.unlock(); + + // Setup the ticket for ::Process + ticket->Start(); + + if (ticket->IsCancelled()) { + ticket->Finish(); + } else { + RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); + } + + locker.relock(); + } + } + + if (context_) { + context_->Destroy(); + context_->moveToThread(this->thread()); + } +} + } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index c79c031fc..3100915df 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -35,7 +35,39 @@ namespace olive { -class RenderManager : public ThreadPool +class RenderThread : public QThread +{ + Q_OBJECT +public: + RenderThread(Renderer *renderer, DecoderCache *decoder_cache, ShaderCache *shader_cache, QObject *parent = nullptr); + + void AddTicket(RenderTicketPtr ticket); + + bool RemoveTicket(RenderTicketPtr ticket); + + void quit(); + +protected: + virtual void run() override; + +private: + QMutex mutex_; + + QWaitCondition wait_; + + std::list queue_; + + bool cancelled_; + + Renderer *context_; + + DecoderCache *decoder_cache_; + + ShaderCache *shader_cache_; + +}; + +class RenderManager : public QObject { Q_OBJECT public: @@ -96,7 +128,7 @@ public: */ RenderTicketPtr RenderAudio(Node *viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority = RenderTicketPriority::kNormal); - virtual void RunTicket(RenderTicketPtr ticket) const override; + bool RemoveTicket(RenderTicketPtr ticket); enum TicketType { kTypeVideo, @@ -135,6 +167,9 @@ private: QTimer *decoder_clear_timer_; + RenderThread *video_thread_; + RenderThread *audio_thread_; + private slots: void ClearOldDecoders(); From 63f74691a55f64cd14a0c5d7db84e124e03b0e4f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 25 Jun 2022 09:59:43 -0700 Subject: [PATCH 31/40] render: cleaned up threading --- app/CMakeLists.txt | 3 +- app/render/CMakeLists.txt | 4 +- app/render/previewautocacher.cpp | 19 +- app/render/previewautocacher.h | 18 +- app/render/rendererthreadwrapper.cpp | 181 ------------------ app/render/rendererthreadwrapper.h | 86 --------- app/render/rendermanager.cpp | 8 +- app/render/rendermanager.h | 8 +- app/render/renderprocessor.h | 2 +- .../renderticket.cpp} | 80 +++++++- .../threadticket.h => render/renderticket.h} | 34 ++++ app/task/render/render.h | 3 +- app/threading/CMakeLists.txt | 26 --- app/threading/threadpool.cpp | 110 ----------- app/threading/threadpool.h | 70 ------- app/threading/threadticketwatcher.cpp | 103 ---------- app/threading/threadticketwatcher.h | 64 ------- app/widget/viewer/viewer.cpp | 14 +- app/widget/viewer/viewer.h | 5 +- 19 files changed, 150 insertions(+), 688 deletions(-) delete mode 100644 app/render/rendererthreadwrapper.cpp delete mode 100644 app/render/rendererthreadwrapper.h rename app/{threading/threadticket.cpp => render/renderticket.cpp} (61%) rename app/{threading/threadticket.h => render/renderticket.h} (87%) delete mode 100644 app/threading/CMakeLists.txt delete mode 100644 app/threading/threadpool.cpp delete mode 100644 app/threading/threadpool.h delete mode 100644 app/threading/threadticketwatcher.cpp delete mode 100644 app/threading/threadticketwatcher.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d438dc135..35a818bc0 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -35,7 +35,6 @@ add_subdirectory(panel) add_subdirectory(render) add_subdirectory(shaders) add_subdirectory(task) -add_subdirectory(threading) add_subdirectory(timeline) add_subdirectory(ts) add_subdirectory(tool) @@ -99,7 +98,7 @@ if (WIN32) # Set Windows application icon target_sources(olive-editor PRIVATE packaging/windows/resources.rc) - # Preserve folder structure in visual studio + # Preserve folder structure in visual studio source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES}) elseif(APPLE) diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 8a37e1776..d8a9a9a22 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -46,8 +46,6 @@ set(OLIVE_SOURCES render/renderer.cpp render/renderer.h render/rendercache.h - render/rendererthreadwrapper.cpp - render/rendererthreadwrapper.h render/renderjobtracker.cpp render/renderjobtracker.h render/rendermanager.cpp @@ -55,6 +53,8 @@ set(OLIVE_SOURCES render/rendermodes.h render/renderprocessor.cpp render/renderprocessor.h + render/renderticket.cpp + render/renderticket.h render/shadercode.h render/subtitleparams.cpp render/subtitleparams.h diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index e27dc2cf7..d5707c845 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -61,7 +61,7 @@ PreviewAutoCacher::~PreviewAutoCacher() SetViewerNode(nullptr); } -RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicketPriority priority) +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) { // If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now CancelQueuedSingleFrameRender(); @@ -70,7 +70,6 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicke auto sfr = std::make_shared(); sfr->Start(); sfr->setProperty("time", QVariant::fromValue(t)); - sfr->setProperty("priority", int(priority)); // Queue it and try to render single_frame_render_ = sfr; @@ -79,9 +78,9 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicke return sfr; } -RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicketPriority priority) +RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range) { - return RenderAudio(range, false, priority); + return RenderAudio(range, false); } void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) @@ -567,7 +566,6 @@ void PreviewAutoCacher::TryRender() if (single_frame_render_) { // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), - RenderTicketPriority(single_frame_render_->property("priority").toInt()), nullptr); video_immediate_passthroughs_[watcher].append(single_frame_render_); @@ -585,7 +583,7 @@ void PreviewAutoCacher::TryRender() // We want this hash, if we're not already rendering, start render now if (!render_task) { // Don't render any hash more than once - RenderFrame(t, RenderTicketPriority::kNormal, viewer_node_->video_frame_cache()); + RenderFrame(t, viewer_node_->video_frame_cache()); } emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); @@ -605,13 +603,13 @@ void PreviewAutoCacher::TryRender() r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); // Start job - RenderAudio(r, true, RenderTicketPriority::kNormal); + RenderAudio(r, true); audio_iterator_.remove(r); } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderTicketPriority priority, FrameHashCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); @@ -625,19 +623,18 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderMode::kOffline, cache, - priority, RenderManager::kTexture)); return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, bool generate_waveforms, RenderTicketPriority priority) +RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, bool generate_waveforms) { 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(node, r, copied_viewer_node_->GetAudioParams(), RenderMode::kOffline, generate_waveforms, priority); + RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(node, r, copied_viewer_node_->GetAudioParams(), RenderMode::kOffline, generate_waveforms); watcher->SetTicket(ticket); return ticket; } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 4c20a72d6..c9343021f 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -33,8 +33,6 @@ #include "render/audioparams.h" #include "render/renderjobtracker.h" #include "render/rendermanager.h" -#include "threading/threadpool.h" -#include "threading/threadticketwatcher.h" namespace olive { @@ -51,9 +49,9 @@ public: virtual ~PreviewAutoCacher() override; - RenderTicketPtr GetSingleFrame(const rational& t, RenderTicketPriority prioritize); + RenderTicketPtr GetSingleFrame(const rational& t); - RenderTicketPtr GetRangeOfAudio(TimeRange range, RenderTicketPriority prioritize); + RenderTicketPtr GetRangeOfAudio(TimeRange range); /** * @brief Set the viewer node to auto-cache @@ -100,16 +98,16 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, RenderTicketPriority priority, FrameHashCache *cache); - RenderTicketWatcher *RenderFrame(const rational &time, RenderTicketPriority priority, FrameHashCache *cache) + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache); + RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache) { - return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, priority, cache); + return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache); } - RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms, RenderTicketPriority priority); - RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms, RenderTicketPriority priority) + RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms); + RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms) { - return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, generate_waveforms, priority); + return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, generate_waveforms); } /** diff --git a/app/render/rendererthreadwrapper.cpp b/app/render/rendererthreadwrapper.cpp deleted file mode 100644 index 50abd8b77..000000000 --- a/app/render/rendererthreadwrapper.cpp +++ /dev/null @@ -1,181 +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 "rendererthreadwrapper.h" - -namespace olive { - -RendererThreadWrapper::RendererThreadWrapper(Renderer *inner, QObject *parent) : - Renderer(parent), - inner_(inner), - thread_(nullptr) -{ -} - -bool RendererThreadWrapper::Init() -{ - // Init context in main thread - if (!inner_->Init()) { - return false; - } - - // Create thread - thread_ = new QThread(this); - thread_->start(QThread::IdlePriority); - - // Move context to thread - inner_->moveToThread(thread_); - - // Queue post-init in new thread - QMetaObject::invokeMethod(inner_, "PostInit", Qt::BlockingQueuedConnection); - - return true; -} - -void RendererThreadWrapper::PostInit() -{ - // Do nothing -} - -void RendererThreadWrapper::DestroyInternal() -{ - if (thread_) { - QMetaObject::invokeMethod(inner_, "DestroyInternal", Qt::BlockingQueuedConnection); - - thread_->quit(); - thread_->wait(); - delete thread_; - thread_ = nullptr; - - // Destroy in main thread - inner_->PostDestroy(); - } -} - -void RendererThreadWrapper::ClearDestination(Texture *texture, double r, double g, double b, double a) -{ - QMetaObject::invokeMethod(inner_, "ClearDestination", Qt::BlockingQueuedConnection, - OLIVE_NS_ARG(Texture*, texture), - Q_ARG(double, r), - Q_ARG(double, g), - Q_ARG(double, b), - Q_ARG(double, a)); -} - -QVariant RendererThreadWrapper::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - QVariant v; - - QMetaObject::invokeMethod(inner_, "CreateNativeTexture2D", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, v), - Q_ARG(int, width), - Q_ARG(int, height), - OLIVE_NS_ARG(VideoParams::Format, format), - Q_ARG(int, channel_count), - Q_ARG(const void*, data), - Q_ARG(int, linesize)); - - return v; -} - -QVariant RendererThreadWrapper::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - QVariant v; - - QMetaObject::invokeMethod(inner_, "CreateNativeTexture3D", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, v), - Q_ARG(int, width), - Q_ARG(int, height), - Q_ARG(int, depth), - OLIVE_NS_ARG(VideoParams::Format, format), - Q_ARG(int, channel_count), - Q_ARG(const void*, data), - Q_ARG(int, linesize)); - - return v; -} - -void RendererThreadWrapper::DestroyNativeTexture(QVariant texture) -{ - QMetaObject::invokeMethod(inner_, "DestroyNativeTexture", Qt::BlockingQueuedConnection, - Q_ARG(QVariant, texture)); -} - -QVariant RendererThreadWrapper::CreateNativeShader(ShaderCode code) -{ - QVariant v; - - QMetaObject::invokeMethod(inner_, "CreateNativeShader", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, v), - OLIVE_NS_ARG(ShaderCode, code)); - - return v; -} - -void RendererThreadWrapper::DestroyNativeShader(QVariant shader) -{ - QMetaObject::invokeMethod(inner_, "DestroyNativeShader", Qt::BlockingQueuedConnection, - Q_ARG(QVariant, shader)); -} - -void RendererThreadWrapper::UploadToTexture(Texture *texture, const void *data, int linesize) -{ - QMetaObject::invokeMethod(inner_, "UploadToTexture", Qt::BlockingQueuedConnection, - OLIVE_NS_ARG(Texture*, texture), - Q_ARG(const void*, data), - Q_ARG(int, linesize)); -} - -void RendererThreadWrapper::DownloadFromTexture(Texture *texture, void *data, int linesize) -{ - QMetaObject::invokeMethod(inner_, "DownloadFromTexture", Qt::BlockingQueuedConnection, - OLIVE_NS_ARG(Texture*, texture), - Q_ARG(void*, data), - Q_ARG(int, linesize)); -} - -void RendererThreadWrapper::Flush() -{ - QMetaObject::invokeMethod(inner_, "Flush", Qt::BlockingQueuedConnection); -} - -Color RendererThreadWrapper::GetPixelFromTexture(Texture *texture, const QPointF &pt) -{ - Color c; - - QMetaObject::invokeMethod(inner_, "GetPixelFromTexture", Qt::BlockingQueuedConnection, - OLIVE_NS_RETURN_ARG(Color, c), - OLIVE_NS_ARG(Texture*, texture), - Q_ARG(QPointF, pt)); - - return c; -} - -void RendererThreadWrapper::Blit(QVariant shader, ShaderJob job, Texture *destination, VideoParams destination_params, bool clear_destination) -{ - QMetaObject::invokeMethod(inner_, "Blit", Qt::BlockingQueuedConnection, - Q_ARG(QVariant, shader), - OLIVE_NS_ARG(ShaderJob, job), - OLIVE_NS_ARG(Texture*, destination), - OLIVE_NS_ARG(VideoParams, destination_params), - Q_ARG(bool, clear_destination)); -} - -} diff --git a/app/render/rendererthreadwrapper.h b/app/render/rendererthreadwrapper.h deleted file mode 100644 index 873e43832..000000000 --- a/app/render/rendererthreadwrapper.h +++ /dev/null @@ -1,86 +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 RENDERCONTEXTTHREADWRAPPER_H -#define RENDERCONTEXTTHREADWRAPPER_H - -#include - -#include "renderer.h" - -namespace olive { - -class RendererThreadWrapper : public Renderer -{ -public: - RendererThreadWrapper(Renderer* inner, QObject* parent = nullptr); - - virtual ~RendererThreadWrapper() override - { - Destroy(); - PostDestroy(); - delete inner_; - } - - virtual bool Init() override; - - virtual void PostDestroy() override {} - -public slots: - virtual void PostInit() override; - - virtual void DestroyInternal() override; - - virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) override; - - virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - - virtual void DestroyNativeTexture(QVariant texture) override; - - virtual QVariant CreateNativeShader(olive::ShaderCode code) override; - - virtual void DestroyNativeShader(QVariant shader) override; - - virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) override; - - virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) override; - - virtual void Flush() override; - - virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) override; - -protected slots: - virtual void Blit(QVariant shader, - olive::ShaderJob job, - olive::Texture* destination, - olive::VideoParams destination_params, - bool clear_destination) override; - -private: - Renderer* inner_; - - QThread* thread_; - -}; - -} - -#endif // RENDERCONTEXTTHREADWRAPPER_H diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 2c4be2b1b..202820be5 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -27,7 +27,6 @@ #include "config/config.h" #include "core.h" #include "render/opengl/openglrenderer.h" -#include "render/rendererthreadwrapper.h" #include "renderprocessor.h" #include "task/conform/conform.h" #include "task/taskmanager.h" @@ -84,7 +83,7 @@ RenderManager::~RenderManager() RenderTicketPtr RenderManager::RenderFrame(Node *node, const VideoParams &vparam, const AudioParams ¶m, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, - FrameHashCache* cache, RenderTicketPriority priority, ReturnType return_type) + FrameHashCache* cache, ReturnType return_type) { return RenderFrame(node, color_manager, @@ -97,7 +96,6 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, const VideoParams &vparam VideoParams::kFormatInvalid, nullptr, cache, - priority, return_type); } @@ -107,7 +105,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output, - FrameHashCache* cache, RenderTicketPriority priority, ReturnType return_type) + FrameHashCache* cache, ReturnType return_type) { // Create ticket RenderTicketPtr ticket = std::make_shared(); @@ -136,7 +134,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag return ticket; } -RenderTicketPtr RenderManager::RenderAudio(Node *node, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority) +RenderTicketPtr RenderManager::RenderAudio(Node *node, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms) { // Create ticket RenderTicketPtr ticket = std::make_shared(); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 3100915df..a75c58aaf 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -30,8 +30,8 @@ #include "node/output/viewer/viewer.h" #include "node/traverser.h" #include "render/renderer.h" +#include "render/renderticket.h" #include "rendercache.h" -#include "threading/threadpool.h" namespace olive { @@ -110,14 +110,14 @@ public: */ RenderTicketPtr RenderFrame(Node *node, const VideoParams &vparam, const AudioParams ¶m, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, - FrameHashCache* cache = nullptr, RenderTicketPriority priority = RenderTicketPriority::kNormal, ReturnType return_type = kFrame); + FrameHashCache* cache = nullptr, ReturnType return_type = kFrame); RenderTicketPtr RenderFrame(Node *node, 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, RenderTicketPriority priority = RenderTicketPriority::kNormal, ReturnType return_type = kFrame); + FrameHashCache* cache = nullptr, ReturnType return_type = kFrame); /** * @brief Asynchronously generate a chunk of audio @@ -126,7 +126,7 @@ public: * * This function is thread-safe. */ - RenderTicketPtr RenderAudio(Node *viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority = RenderTicketPriority::kNormal); + RenderTicketPtr RenderAudio(Node *viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms); bool RemoveTicket(RenderTicketPtr ticket); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index f0ef31227..73e871880 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -25,7 +25,7 @@ #include "node/traverser.h" #include "render/renderer.h" #include "rendercache.h" -#include "threading/threadticket.h" +#include "renderticket.h" namespace olive { diff --git a/app/threading/threadticket.cpp b/app/render/renderticket.cpp similarity index 61% rename from app/threading/threadticket.cpp rename to app/render/renderticket.cpp index 8f9715424..beba8b808 100644 --- a/app/threading/threadticket.cpp +++ b/app/render/renderticket.cpp @@ -18,7 +18,7 @@ ***/ -#include "threadticket.h" +#include "renderticket.h" namespace olive { @@ -128,4 +128,82 @@ void RenderTicket::FinishInternal(bool has_result, QVariant result) } } +RenderTicketWatcher::RenderTicketWatcher(QObject *parent) : + QObject(parent), + ticket_(nullptr) +{ +} + +void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket) +{ + if (ticket_) { + qCritical() << "Tried to set a ticket on a RenderTicketWatcher twice"; + return; + } + + if (!ticket) { + qCritical() << "Tried to set a null ticket on a RenderTicketWatcher"; + return; + } + + ticket_ = ticket; + + // Lock ticket so we can query if it's already finished by the time this code runs + QMutexLocker locker(ticket->lock()); + + connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::TicketFinished); + + if (!ticket_->IsRunning(false) && ticket_->GetFinishCount(false) > 0) { + // Ticket has already finished before, so we emit a signal + locker.unlock(); + TicketFinished(); + } +} + +bool RenderTicketWatcher::IsRunning() +{ + if (ticket_) { + return ticket_->IsRunning(); + } else { + return false; + } +} + +void RenderTicketWatcher::WaitForFinished() +{ + if (ticket_) { + ticket_->WaitForFinished(); + } +} + +QVariant RenderTicketWatcher::Get() +{ + if (ticket_) { + return ticket_->Get(); + } else { + return QVariant(); + } +} + +bool RenderTicketWatcher::HasResult() +{ + if (ticket_) { + return ticket_->HasResult(); + } else { + return false; + } +} + +void RenderTicketWatcher::Cancel() +{ + if (ticket_) { + ticket_->Cancel(); + } +} + +void RenderTicketWatcher::TicketFinished() +{ + emit Finished(this); +} + } diff --git a/app/threading/threadticket.h b/app/render/renderticket.h similarity index 87% rename from app/threading/threadticket.h rename to app/render/renderticket.h index fb42ab969..700053653 100644 --- a/app/threading/threadticket.h +++ b/app/render/renderticket.h @@ -132,6 +132,40 @@ private: using RenderTicketPtr = std::shared_ptr; +class RenderTicketWatcher : public QObject +{ + Q_OBJECT +public: + RenderTicketWatcher(QObject* parent = nullptr); + + RenderTicketPtr GetTicket() const + { + return ticket_; + } + + void SetTicket(RenderTicketPtr ticket); + + bool IsRunning(); + + void WaitForFinished(); + + QVariant Get(); + + bool HasResult(); + + void Cancel(); + +signals: + void Finished(RenderTicketWatcher* watcher); + +private: + RenderTicketPtr ticket_; + +private slots: + void TicketFinished(); + +}; + } Q_DECLARE_METATYPE(olive::RenderTicketPtr) diff --git a/app/task/render/render.h b/app/task/render/render.h index d08c6d018..2609bc3d9 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -27,8 +27,7 @@ #include "node/color/colormanager/colormanager.h" #include "node/output/viewer/viewer.h" #include "task/task.h" -#include "threading/threadticket.h" -#include "threading/threadticketwatcher.h" +#include "render/renderticket.h" namespace olive { diff --git a/app/threading/CMakeLists.txt b/app/threading/CMakeLists.txt deleted file mode 100644 index ada17c49c..000000000 --- a/app/threading/CMakeLists.txt +++ /dev/null @@ -1,26 +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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - threading/threadticket.cpp - threading/threadticket.h - threading/threadticketwatcher.cpp - threading/threadticketwatcher.h - threading/threadpool.cpp - threading/threadpool.h - PARENT_SCOPE -) diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp deleted file mode 100644 index d65f6c574..000000000 --- a/app/threading/threadpool.cpp +++ /dev/null @@ -1,110 +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 "threadpool.h" - -namespace olive { - -ThreadPool::ThreadPool(unsigned threads, QObject *parent) : - QObject(parent) -{ - if (threads == 0) { - threads = std::thread::hardware_concurrency(); - } - - for (unsigned i = 0; i < threads; i += 1) { - worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this, &tasks_, &task_mutex_, &cond_)); - } - - // Make single reserved thread for high priority tasks (usually audio) so they don't get stuck - // behind a lot of slow tasks - high_thread_ = std::thread(std::thread(std::bind(&ThreadPool::thread_exec, this, &high_tasks_, &high_mutex_, &high_cond_))); -} - -void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority) -{ - if (priority == RenderTicketPriority::kHigh) { - std::lock_guard lock(high_mutex_); - high_tasks_.emplace_back(std::move(ticket)); - high_cond_.notify_one(); - } else { - std::lock_guard lock(task_mutex_); - tasks_.emplace_back(std::move(ticket)); - cond_.notify_one(); - } -} - -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()) { - tasks_.erase(it); - return true; - } - } - - { - std::lock_guard lock(high_mutex_); - const auto it = std::find(high_tasks_.begin(), high_tasks_.end(), ticket); - if (it != high_tasks_.end()) { - high_tasks_.erase(it); - return true; - } - } - - return false; -} - -void ThreadPool::thread_exec(std::deque *queue, std::mutex *mutex, std::condition_variable *cond) -{ - while (true) { - TaskType task; - - { - std::unique_lock lock(*mutex); - cond->wait(lock, [this, queue]{ return this->end_threadp_ || !queue->empty(); }); - - if (this->end_threadp_ && queue->empty()) { - break; - } - - task = std::move(queue->front()); - queue->pop_front(); - } - - RunTicket(task); - } -} - -ThreadPool::~ThreadPool() -{ - end_threadp_ = true; - cond_.notify_all(); - high_cond_.notify_all(); - - for (auto &e : worker_threads_) { - e.join(); - } - high_thread_.join(); -} - -} diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h deleted file mode 100644 index e4dcb18e1..000000000 --- a/app/threading/threadpool.h +++ /dev/null @@ -1,70 +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 THREADPOOL_H -#define THREADPOOL_H - -#include "threading/threadticket.h" - -#include -#include -#include -#include -#include - -namespace olive { - -enum class RenderTicketPriority { kHigh = 0, kNormal }; - -class ThreadPool : public QObject -{ - Q_OBJECT -public: - using TaskType = RenderTicketPtr; - ThreadPool(unsigned threads, QObject *parent); - - DISABLE_COPY_MOVE(ThreadPool) - - virtual void RunTicket(RenderTicketPtr ticket) const = 0; - void AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority = RenderTicketPriority::kNormal); - bool RemoveTicket(RenderTicketPtr ticket); - - virtual ~ThreadPool() override; - -private: - void thread_exec(std::deque *queue, std::mutex *mutex, std::condition_variable *cond); - - std::vector worker_threads_; - std::deque tasks_; - std::mutex task_mutex_; - std::condition_variable cond_; - - std::thread high_thread_; - std::deque high_tasks_; - std::mutex high_mutex_; - std::condition_variable high_cond_; - - std::atomic_bool end_threadp_{false}; - -}; - -} // namespace olive - -#endif // THREADPOOL_H diff --git a/app/threading/threadticketwatcher.cpp b/app/threading/threadticketwatcher.cpp deleted file mode 100644 index a6ca5b2eb..000000000 --- a/app/threading/threadticketwatcher.cpp +++ /dev/null @@ -1,103 +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 "threadticketwatcher.h" - -namespace olive { - -RenderTicketWatcher::RenderTicketWatcher(QObject *parent) : - QObject(parent), - ticket_(nullptr) -{ -} - -void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket) -{ - if (ticket_) { - qCritical() << "Tried to set a ticket on a RenderTicketWatcher twice"; - return; - } - - if (!ticket) { - qCritical() << "Tried to set a null ticket on a RenderTicketWatcher"; - return; - } - - ticket_ = ticket; - - // Lock ticket so we can query if it's already finished by the time this code runs - QMutexLocker locker(ticket->lock()); - - connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::TicketFinished); - - if (!ticket_->IsRunning(false) && ticket_->GetFinishCount(false) > 0) { - // Ticket has already finished before, so we emit a signal - locker.unlock(); - TicketFinished(); - } -} - -bool RenderTicketWatcher::IsRunning() -{ - if (ticket_) { - return ticket_->IsRunning(); - } else { - return false; - } -} - -void RenderTicketWatcher::WaitForFinished() -{ - if (ticket_) { - ticket_->WaitForFinished(); - } -} - -QVariant RenderTicketWatcher::Get() -{ - if (ticket_) { - return ticket_->Get(); - } else { - return QVariant(); - } -} - -bool RenderTicketWatcher::HasResult() -{ - if (ticket_) { - return ticket_->HasResult(); - } else { - return false; - } -} - -void RenderTicketWatcher::Cancel() -{ - if (ticket_) { - ticket_->Cancel(); - } -} - -void RenderTicketWatcher::TicketFinished() -{ - emit Finished(this); -} - -} diff --git a/app/threading/threadticketwatcher.h b/app/threading/threadticketwatcher.h deleted file mode 100644 index 3b442e03e..000000000 --- a/app/threading/threadticketwatcher.h +++ /dev/null @@ -1,64 +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 RENDERTICKETWATCHER_H -#define RENDERTICKETWATCHER_H - -#include "threadticket.h" - -namespace olive { - -class RenderTicketWatcher : public QObject -{ - Q_OBJECT -public: - RenderTicketWatcher(QObject* parent = nullptr); - - RenderTicketPtr GetTicket() const - { - return ticket_; - } - - void SetTicket(RenderTicketPtr ticket); - - bool IsRunning(); - - void WaitForFinished(); - - QVariant Get(); - - bool HasResult(); - - void Cancel(); - -signals: - void Finished(RenderTicketWatcher* watcher); - -private: - RenderTicketPtr ticket_; - -private slots: - void TicketFinished(); - -}; - -} - -#endif // RENDERTICKETWATCHER_H diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index dedca355c..f9bf5a8e0 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -578,7 +578,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), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end))); audio_playback_queue_time_ = queue_end; } @@ -735,7 +735,7 @@ void ViewerWidget::UpdateTextureFromNode() ClearVideoAutoCacherQueue(); } - watcher->SetTicket(GetFrame(time, RenderTicketPriority::kNormal)); + watcher->SetTicket(GetFrame(time)); } else { // There is definitely no frame here, we can immediately flip to showing nothing nonqueue_watchers_.clear(); @@ -889,7 +889,7 @@ void ViewerWidget::PushScrubbedAudio() RenderTicketWatcher *watcher = new RenderTicketWatcher(); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval))); } } } @@ -939,7 +939,7 @@ void ViewerWidget::SetDisplayImage(QVariant frame) } } -RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool increment) +RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment) { RenderTicketWatcher *watcher = nullptr; @@ -955,19 +955,19 @@ RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority watcher->setProperty("time", QVariant::fromValue(next_time)); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); queue_watchers_.append(watcher); - watcher->SetTicket(GetFrame(next_time, priority)); + watcher->SetTicket(GetFrame(next_time)); } return watcher; } -RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority priority) +RenderTicketPtr ViewerWidget::GetFrame(const rational &t) { QString cache_fn = GetConnectedNode()->video_frame_cache()->GetValidCacheFilename(t); if (!QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job - return auto_cacher_.GetSingleFrame(t, priority); + return auto_cacher_.GetSingleFrame(t); } 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 ebb5c7ba5..d38ef5316 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -34,7 +34,6 @@ #include "node/output/viewer/viewer.h" #include "render/previewaudiodevice.h" #include "render/previewautocacher.h" -#include "threading/threadticketwatcher.h" #include "viewerdisplay.h" #include "viewersizer.h" #include "viewerwindow.h" @@ -202,9 +201,9 @@ private: void SetDisplayImage(QVariant frame); - RenderTicketWatcher *RequestNextFrameForQueue(RenderTicketPriority priority = RenderTicketPriority::kNormal, bool increment = true); + RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true); - RenderTicketPtr GetFrame(const rational& t, RenderTicketPriority priority); + RenderTicketPtr GetFrame(const rational& t); void FinishPlayPreprocess(); From 079d6beff213e0074c146fad49bfecc83a1c1eb3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Jun 2022 23:39:31 -0700 Subject: [PATCH 32/40] timeline: separate markers and workarea --- app/dialog/export/export.cpp | 7 +- app/node/block/clip/clip.cpp | 12 +- app/node/output/viewer/viewer.cpp | 3 +- app/node/output/viewer/viewer.h | 12 +- app/node/project/footage/footage.h | 1 - app/node/project/sequence/sequence.h | 1 - .../project/serializer/serializer210528.cpp | 8 +- .../project/serializer/serializer210528.h | 2 +- .../project/serializer/serializer210907.cpp | 8 +- .../project/serializer/serializer210907.h | 2 +- .../project/serializer/serializer211228.cpp | 8 +- .../project/serializer/serializer211228.h | 2 +- .../project/serializer/serializer220403.cpp | 16 +- .../project/serializer/serializer220403.h | 4 +- app/panel/timeline/timeline.cpp | 1 + app/panel/timeline/timeline.h | 1 + app/task/precache/precachetask.cpp | 4 +- app/timeline/CMakeLists.txt | 2 - app/timeline/timelinepoints.cpp | 54 ----- app/timeline/timelinepoints.h | 53 ----- .../resizabletimelinescrollbar.cpp | 67 +++--- .../resizabletimelinescrollbar.h | 10 +- app/widget/timebased/timebasedwidget.cpp | 77 ++++--- app/widget/timebased/timebasedwidget.h | 11 + app/widget/timelinewidget/timelinewidget.cpp | 14 +- app/widget/timelinewidget/tool/import.cpp | 2 +- .../undo/timelineundoworkarea.h | 11 +- .../timelinewidget/view/timelineview.cpp | 2 +- app/widget/timeruler/seekablewidget.cpp | 214 ++++++++++-------- app/widget/timeruler/seekablewidget.h | 17 +- app/widget/timeruler/timeruler.cpp | 5 +- app/widget/viewer/audiowaveformview.cpp | 3 +- app/widget/viewer/viewer.cpp | 26 ++- app/widget/viewer/viewer.h | 2 + 34 files changed, 324 insertions(+), 338 deletions(-) delete mode 100644 app/timeline/timelinepoints.cpp delete mode 100644 app/timeline/timelinepoints.h diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index d805f8726..f93bb6177 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -107,7 +107,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : range_combobox_ = new QComboBox(); range_combobox_->addItem(tr("Entire Sequence")); range_combobox_->addItem(tr("In to Out")); - range_combobox_->setEnabled(viewer_node_->GetTimelinePoints()->workarea()->enabled()); + range_combobox_->setEnabled(viewer_node_->GetWorkArea()->enabled()); preferences_layout->addWidget(range_combobox_, row, 1, 1, 3); @@ -247,7 +247,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : // Set viewer to view the node preview_viewer_->ConnectViewerNode(viewer_node_); - preview_viewer_->ruler()->ConnectTimelinePoints(viewer_node_->GetTimelinePoints()); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); } @@ -529,7 +528,7 @@ ExportParams ExportDialog::GenerateParams() const params.set_custom_range(TimeRange(export_time, export_time + GetSelectedTimebase())); } else if (range_combobox_->currentIndex() == kRangeInToOut) { // Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor - params.set_custom_range(viewer_node_->GetTimelinePoints()->workarea()->range()); + params.set_custom_range(viewer_node_->GetWorkArea()->range()); } if (video_tab_->scaling_method_combobox()->isEnabled()) { @@ -570,7 +569,7 @@ ExportParams ExportDialog::GenerateParams() const rational ExportDialog::GetExportLength() const { if (range_combobox_->currentIndex() == kRangeInToOut) { - return viewer_node_->GetTimelinePoints()->workarea()->range().length(); + return viewer_node_->GetWorkArea()->range().length(); } else { return viewer_node_->GetLength(); } diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 241484965..080850c45 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -204,17 +204,17 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int if (new_connected_viewer != connected_viewer_) { if (connected_viewer_) { - disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); - disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); - disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); + disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); + disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); + disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); } connected_viewer_ = new_connected_viewer; if (connected_viewer_) { - connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); - connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); - connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); + connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); + connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); + connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); } } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index a9476a101..b505a8354 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -58,7 +58,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream SetFlags(kDontShowInParamView); - timeline_points_ = new TimelinePoints(this); + workarea_ = new TimelineWorkArea(this); + markers_ = new TimelineMarkerList(this); } QString ViewerOutput::Name() const diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index c68225df8..bc30cbc8a 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -29,7 +29,8 @@ #include "render/framehashcache.h" #include "render/subtitleparams.h" #include "render/videoparams.h" -#include "timeline/timelinepoints.h" +#include "timeline/timelinemarker.h" +#include "timeline/timelineworkarea.h" namespace olive { @@ -142,10 +143,8 @@ public: const rational &GetVideoLength() const { return video_length_; } const rational &GetAudioLength() const { return audio_length_; } - TimelinePoints* GetTimelinePoints() - { - return timeline_points_; - } + TimelineWorkArea *GetWorkArea() const { return workarea_; } + TimelineMarkerList *GetMarkers() const { return markers_; } QVector GetEnabledStreamsAsReferences() const; @@ -212,7 +211,8 @@ private: AudioParams cached_audio_params_; - TimelinePoints *timeline_points_; + TimelineWorkArea *workarea_; + TimelineMarkerList *markers_; }; diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 4db9cdf59..43e5f80c7 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -29,7 +29,6 @@ #include "node/output/viewer/viewer.h" #include "render/audioparams.h" #include "render/videoparams.h" -#include "timeline/timelinepoints.h" namespace olive { diff --git a/app/node/project/sequence/sequence.h b/app/node/project/sequence/sequence.h index 25220a4f5..4f8b62d04 100644 --- a/app/node/project/sequence/sequence.h +++ b/app/node/project/sequence/sequence.h @@ -23,7 +23,6 @@ #include "node/output/track/tracklist.h" #include "node/output/viewer/viewer.h" -#include "timeline/timelinepoints.h" namespace olive { diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index f0ad798ad..ea30b7578 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -496,7 +496,7 @@ void ProjectSerializer210528::LoadNodeCustom(QXmlStreamReader *reader, Node *nod while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("points")) { - LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + LoadTimelinePoints(reader, viewer); } else if (reader->name() == QStringLiteral("timestamp") && footage) { footage->set_timestamp(reader->readElementText().toLongLong()); } else { @@ -553,13 +553,13 @@ void ProjectSerializer210528::LoadNodeCustom(QXmlStreamReader *reader, Node *nod } } -void ProjectSerializer210528::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +void ProjectSerializer210528::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("markers")) { - LoadMarkerList(reader, points->markers()); + LoadMarkerList(reader, points->GetMarkers()); } else if (reader->name() == QStringLiteral("workarea")) { - LoadWorkArea(reader, points->workarea()); + LoadWorkArea(reader, points->GetWorkArea()); } else { reader->skipCurrentElement(); } diff --git a/app/node/project/serializer/serializer210528.h b/app/node/project/serializer/serializer210528.h index 2bf195064..537b89cf4 100644 --- a/app/node/project/serializer/serializer210528.h +++ b/app/node/project/serializer/serializer210528.h @@ -78,7 +78,7 @@ private: void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; - void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const; void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index edcb86ae3..dc5e127e7 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -488,7 +488,7 @@ void ProjectSerializer210907::LoadNodeCustom(QXmlStreamReader *reader, Node *nod while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("points")) { - LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + LoadTimelinePoints(reader, viewer); } else if (reader->name() == QStringLiteral("timestamp") && footage) { footage->set_timestamp(reader->readElementText().toLongLong()); } else { @@ -545,13 +545,13 @@ void ProjectSerializer210907::LoadNodeCustom(QXmlStreamReader *reader, Node *nod } } -void ProjectSerializer210907::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +void ProjectSerializer210907::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("markers")) { - LoadMarkerList(reader, points->markers()); + LoadMarkerList(reader, points->GetMarkers()); } else if (reader->name() == QStringLiteral("workarea")) { - LoadWorkArea(reader, points->workarea()); + LoadWorkArea(reader, points->GetWorkArea()); } else { reader->skipCurrentElement(); } diff --git a/app/node/project/serializer/serializer210907.h b/app/node/project/serializer/serializer210907.h index 6a56d43b5..c3e8033de 100644 --- a/app/node/project/serializer/serializer210907.h +++ b/app/node/project/serializer/serializer210907.h @@ -77,7 +77,7 @@ private: void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; - void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const; void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index 41254b105..94a6ce1ed 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -538,7 +538,7 @@ void ProjectSerializer211228::LoadNodeCustom(QXmlStreamReader *reader, Node *nod while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("points")) { - LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + LoadTimelinePoints(reader, viewer); } else if (reader->name() == QStringLiteral("timestamp") && footage) { footage->set_timestamp(reader->readElementText().toLongLong()); } else { @@ -595,13 +595,13 @@ void ProjectSerializer211228::LoadNodeCustom(QXmlStreamReader *reader, Node *nod } } -void ProjectSerializer211228::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +void ProjectSerializer211228::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("markers")) { - LoadMarkerList(reader, points->markers()); + LoadMarkerList(reader, points->GetMarkers()); } else if (reader->name() == QStringLiteral("workarea")) { - LoadWorkArea(reader, points->workarea()); + LoadWorkArea(reader, points->GetWorkArea()); } else { reader->skipCurrentElement(); } diff --git a/app/node/project/serializer/serializer211228.h b/app/node/project/serializer/serializer211228.h index 49733a5d7..bc424bc4c 100644 --- a/app/node/project/serializer/serializer211228.h +++ b/app/node/project/serializer/serializer211228.h @@ -78,7 +78,7 @@ private: void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; - void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *points) const; void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 7dd4ae5c3..a8fd3c88c 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -989,7 +989,7 @@ void ProjectSerializer220403::LoadNodeCustom(QXmlStreamReader *reader, Node *nod while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("points")) { - LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + LoadTimelinePoints(reader, viewer); } else if (reader->name() == QStringLiteral("timestamp") && footage) { footage->set_timestamp(reader->readElementText().toLongLong()); } else { @@ -1084,7 +1084,7 @@ void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod if (ViewerOutput *viewer = dynamic_cast(node)) { // Write TimelinePoints writer->writeStartElement(QStringLiteral("points")); - SaveTimelinePoints(writer, viewer->GetTimelinePoints()); + SaveTimelinePoints(writer, viewer); writer->writeEndElement(); // points if (Footage *footage = dynamic_cast(node)) { @@ -1136,27 +1136,27 @@ void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod } } -void ProjectSerializer220403::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +void ProjectSerializer220403::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("markers")) { - LoadMarkerList(reader, points->markers()); + LoadMarkerList(reader, viewer->GetMarkers()); } else if (reader->name() == QStringLiteral("workarea")) { - LoadWorkArea(reader, points->workarea()); + LoadWorkArea(reader, viewer->GetWorkArea()); } else { reader->skipCurrentElement(); } } } -void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const +void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const { writer->writeStartElement(QStringLiteral("workarea")); - SaveWorkArea(writer, points->workarea()); + SaveWorkArea(writer, viewer->GetWorkArea()); writer->writeEndElement(); // workarea writer->writeStartElement(QStringLiteral("markers")); - SaveMarkerList(writer, points->markers()); + SaveMarkerList(writer, viewer->GetMarkers()); writer->writeEndElement(); // markers } diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index 4612b2fce..fbeb4bfc2 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -100,9 +100,9 @@ private: void SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const; - void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const; - void SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const; + void SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const; void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index f08f7b998..686aec085 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -36,6 +36,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) : connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged); connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart); connect(tw, &TimelineWidget::RevealViewerInProject, this, &TimelinePanel::RevealViewerInProject); + connect(tw, &TimelineWidget::RevealViewerInFootageViewer, this, &TimelinePanel::RevealViewerInFootageViewer); } void TimelinePanel::SplitAtPlayhead() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 90de35332..cf596a724 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -109,6 +109,7 @@ signals: void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); void RevealViewerInProject(ViewerOutput *r); + void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); }; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index e83de623b..b0145474c 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -65,9 +65,9 @@ bool PreCacheTask::Run() // Get list of invalidated ranges TimeRange intersection; - if (footage_->GetTimelinePoints()->workarea()->enabled()) { + if (footage_->GetWorkArea()->enabled()) { // If we're caching only in-out, limit the range to that - intersection = footage_->GetTimelinePoints()->workarea()->range(); + intersection = footage_->GetWorkArea()->range(); } else { // Otherwise use full length intersection = TimeRange(0, footage_->GetVideoLength()); diff --git a/app/timeline/CMakeLists.txt b/app/timeline/CMakeLists.txt index 3fb988e83..f43d93de3 100644 --- a/app/timeline/CMakeLists.txt +++ b/app/timeline/CMakeLists.txt @@ -21,8 +21,6 @@ set(OLIVE_SOURCES timeline/timelinecoordinate.cpp timeline/timelinemarker.h timeline/timelinemarker.cpp - timeline/timelinepoints.h - timeline/timelinepoints.cpp timeline/timelineworkarea.h timeline/timelineworkarea.cpp PARENT_SCOPE diff --git a/app/timeline/timelinepoints.cpp b/app/timeline/timelinepoints.cpp deleted file mode 100644 index b7c2034bb..000000000 --- a/app/timeline/timelinepoints.cpp +++ /dev/null @@ -1,54 +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 "timelinepoints.h" - -#include "common/xmlutils.h" - -namespace olive { - -TimelinePoints::TimelinePoints(QObject *parent) : - QObject(parent) -{ - markers_ = new TimelineMarkerList(this); - workarea_ = new TimelineWorkArea(this); -} - -TimelineMarkerList *TimelinePoints::markers() -{ - return markers_; -} - -const TimelineMarkerList *TimelinePoints::markers() const -{ - return markers_; -} - -const TimelineWorkArea *TimelinePoints::workarea() const -{ - return workarea_; -} - -TimelineWorkArea *TimelinePoints::workarea() -{ - return workarea_; -} - -} diff --git a/app/timeline/timelinepoints.h b/app/timeline/timelinepoints.h deleted file mode 100644 index 02ea804ce..000000000 --- a/app/timeline/timelinepoints.h +++ /dev/null @@ -1,53 +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 TIMELINEPOINTS_H -#define TIMELINEPOINTS_H - -#include -#include - -#include "timelinemarker.h" -#include "timelineworkarea.h" - -namespace olive { - -class TimelinePoints : public QObject -{ - Q_OBJECT -public: - TimelinePoints(QObject *parent = nullptr); - - TimelineMarkerList* markers(); - const TimelineMarkerList* markers() const; - - TimelineWorkArea* workarea(); - const TimelineWorkArea* workarea() const; - -private: - TimelineMarkerList *markers_; - - TimelineWorkArea *workarea_; - -}; - -} - -#endif // TIMELINEPOINTS_H diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index 4672657bb..2d4f98ea2 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -31,36 +31,51 @@ namespace olive { ResizableTimelineScrollBar::ResizableTimelineScrollBar(QWidget* parent) : ResizableScrollBar(parent), - points_(nullptr), + markers_(nullptr), + workarea_(nullptr), scale_(1.0) { } ResizableTimelineScrollBar::ResizableTimelineScrollBar(Qt::Orientation orientation, QWidget* parent) : ResizableScrollBar(orientation, parent), - points_(nullptr), + markers_(nullptr), + workarea_(nullptr), scale_(1.0) { } -void ResizableTimelineScrollBar::ConnectTimelinePoints(TimelinePoints *points) +void ResizableTimelineScrollBar::ConnectMarkers(TimelineMarkerList *markers) { - if (points_) { - disconnect(points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&ResizableTimelineScrollBar::update)); - disconnect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); - disconnect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); - disconnect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); - disconnect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); + if (markers_) { + disconnect(markers_, &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); + disconnect(markers_, &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); + disconnect(markers_, &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); } - points_ = points; + markers_ = markers; - if (points_) { - connect(points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&ResizableTimelineScrollBar::update)); - connect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); - connect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); - connect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); - connect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); + if (markers_) { + connect(markers_, &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); + connect(markers_, &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); + connect(markers_, &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); + } + + update(); +} + +void ResizableTimelineScrollBar::ConnectWorkArea(TimelineWorkArea *workarea) +{ + if (workarea_) { + disconnect(workarea_, &TimelineWorkArea::RangeChanged, this, static_cast(&ResizableTimelineScrollBar::update)); + disconnect(workarea_, &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); + } + + workarea_ = workarea; + + if (workarea_) { + connect(workarea_, &TimelineWorkArea::RangeChanged, this, static_cast(&ResizableTimelineScrollBar::update)); + connect(workarea_, &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); } update(); @@ -77,9 +92,8 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) { ResizableScrollBar::paintEvent(event); - if (points_ - && !timebase().isNull() - && (points_->workarea()->enabled() || !points_->markers()->empty())) { + if (!timebase().isNull() && ((workarea_ && workarea_->enabled()) || (markers_ && !markers_->empty()))) { + // Draw workarea QStyleOptionSlider opt; initStyleOption(&opt); @@ -87,20 +101,20 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) QStyle::SC_ScrollBarGroove, this); double ratio = scale_ * double(gr.width()) / double(this->maximum() + gr.width()); - QPainter p(this); - if (points_->workarea()->enabled()) { + if (workarea_ && workarea_->enabled()) { + QColor workarea_color(this->palette().highlight().color()); workarea_color.setAlpha(128); - qint64 in = qMax(qint64(0), qRound64(ratio * TimeToScene(points_->workarea()->in()))); + qint64 in = qMax(qint64(0), qRound64(ratio * TimeToScene(workarea_->in()))); qint64 out; - if (points_->workarea()->out() == RATIONAL_MAX) { + if (workarea_->out() == RATIONAL_MAX) { out = gr.width(); } else { - out = qMin(qint64(gr.width()), qRound64(ratio * TimeToScene(points_->workarea()->out()))); + out = qMin(qint64(gr.width()), qRound64(ratio * TimeToScene(workarea_->out()))); } qint64 length = qMax(qint64(1), out-in); @@ -112,8 +126,9 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) workarea_color); } - if (!points_->markers()->empty()) { - for (auto it=points_->markers()->cbegin(); it!=points_->markers()->cend(); it++) { + // Draw markers + if (markers_ && !markers_->empty()) { + for (auto it=markers_->cbegin(); it!=markers_->cend(); it++) { TimelineMarker* marker = *it; QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor(); diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.h b/app/widget/resizablescrollbar/resizabletimelinescrollbar.h index dcd8b10fc..93d7ec197 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.h +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.h @@ -22,7 +22,8 @@ #define RESIZABLETIMELINESCROLLBAR_H #include "resizablescrollbar.h" -#include "timeline/timelinepoints.h" +#include "timeline/timelinemarker.h" +#include "timeline/timelineworkarea.h" #include "widget/timebased/timescaledobject.h" namespace olive { @@ -34,7 +35,8 @@ public: ResizableTimelineScrollBar(QWidget* parent = nullptr); ResizableTimelineScrollBar(Qt::Orientation orientation, QWidget* parent = nullptr); - void ConnectTimelinePoints(TimelinePoints* points); + void ConnectMarkers(TimelineMarkerList *markers); + void ConnectWorkArea(TimelineWorkArea *workarea); void SetScale(double d); @@ -42,7 +44,9 @@ protected: virtual void paintEvent(QPaintEvent* event) override; private: - TimelinePoints* points_; + TimelineMarkerList* markers_; + + TimelineWorkArea* workarea_; double scale_; diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index a812e6c69..ebd95cf9e 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -39,7 +39,9 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu viewer_node_(nullptr), auto_max_scrollbar_(false), toggle_show_all_(false), - auto_set_timebase_(true) + auto_set_timebase_(true), + workarea_(nullptr), + markers_(nullptr) { ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); ConnectTimelineView(ruler_, true); @@ -98,8 +100,8 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) SetTimebase(rational()); // Disconnect ruler and scrollbar from timeline points - ruler()->ConnectTimelinePoints(nullptr); - scrollbar_->ConnectTimelinePoints(nullptr); + ConnectWorkArea(nullptr); + ConnectMarkers(nullptr); } // Call derivatives @@ -111,8 +113,8 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) connect(viewer_node_, &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); // Connect ruler and scrollbar to timeline points - ruler()->ConnectTimelinePoints(viewer_node_->GetTimelinePoints()); - scrollbar_->ConnectTimelinePoints(viewer_node_->GetTimelinePoints()); + ConnectWorkArea(viewer_node_->GetWorkArea()); + ConnectMarkers(viewer_node_->GetMarkers()); // If we're setting the timebase, set it automatically based on the video and audio parameters if (auto_set_timebase_) { @@ -130,6 +132,20 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) emit ConnectedNodeChanged(old, node); } +void TimeBasedWidget::ConnectWorkArea(TimelineWorkArea *workarea) +{ + workarea_ = workarea; + ruler()->SetWorkArea(workarea); + scrollbar_->ConnectWorkArea(workarea); +} + +void TimeBasedWidget::ConnectMarkers(TimelineMarkerList *markers) +{ + markers_ = markers; + ruler()->SetMarkers(markers); + scrollbar_->ConnectMarkers(markers); +} + void TimeBasedWidget::UpdateMaximumScroll() { rational length = (viewer_node_) ? viewer_node_->GetLength() : 0; @@ -374,14 +390,14 @@ void TimeBasedWidget::GoToNextCut() rational closest_cut = RATIONAL_MAX; - foreach (Track* track, sequence->GetTracks()) { + for (Track* track : sequence->GetTracks()) { rational this_track_closest_cut = track->track_length(); if (this_track_closest_cut <= GetTime()) { this_track_closest_cut = RATIONAL_MAX; } - foreach (Block* block, track->Blocks()) { + for (Block* block : track->Blocks()) { if (block->in() > GetTime()) { this_track_closest_cut = block->in(); break; @@ -457,10 +473,10 @@ void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) } MultiUndoCommand* command = new MultiUndoCommand(); - TimelinePoints* points = viewer_node_->GetTimelinePoints(); + TimelineWorkArea* points = viewer_node_->GetWorkArea(); // Enable workarea if it isn't already enabled - if (!points->workarea()->enabled()) { + if (!points->enabled()) { command->add_child(new WorkareaSetEnabledCommand(viewer_node_->project(), points, true)); } @@ -470,23 +486,23 @@ void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) if (m == Timeline::kTrimIn) { in_point = time; - if (!points->workarea()->enabled() || points->workarea()->out() < in_point) { + if (!points->enabled() || points->out() < in_point) { out_point = TimelineWorkArea::kResetOut; } else { - out_point = points->workarea()->out(); + out_point = points->out(); } } else { out_point = time; - if (!points->workarea()->enabled() || points->workarea()->in() > out_point) { + if (!points->enabled() || points->in() > out_point) { in_point = TimelineWorkArea::kResetIn; } else { - in_point = points->workarea()->in(); + in_point = points->in(); } } // Set workarea - command->add_child(new WorkareaSetRangeCommand(points->workarea(), TimeRange(in_point, out_point))); + command->add_child(new WorkareaSetRangeCommand(points, TimeRange(in_point, out_point))); Core::instance()->undo_stack()->push(command); } @@ -497,13 +513,13 @@ void TimeBasedWidget::ResetPoint(Timeline::MovementMode m) return; } - TimelinePoints* points = GetConnectedNode()->GetTimelinePoints(); + TimelineWorkArea* points = GetConnectedNode()->GetWorkArea(); - if (!GetConnectedNode() || !points->workarea()->enabled()) { + if (!points->enabled()) { return; } - TimeRange r = points->workarea()->range(); + TimeRange r = points->range(); if (m == Timeline::kTrimIn) { r.set_in(TimelineWorkArea::kResetIn); @@ -511,7 +527,7 @@ void TimeBasedWidget::ResetPoint(Timeline::MovementMode m) r.set_out(TimelineWorkArea::kResetOut); } - Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points->workarea(), r)); + Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points, r)); } void TimeBasedWidget::PageScrollInternal(QScrollBar *bar, int maximum, int screen_position, bool whole_page_scroll) @@ -582,8 +598,7 @@ void TimeBasedWidget::ClearInOutPoints() return; } - - Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(GetConnectedNode()->project(), GetConnectedNode()->GetTimelinePoints(), false)); + Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(GetConnectedNode()->project(), GetConnectedNode()->GetWorkArea(), false)); } void TimeBasedWidget::SetMarker() @@ -592,7 +607,7 @@ void TimeBasedWidget::SetMarker() return; } - TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers(); + TimelineMarkerList *markers = GetConnectedNode()->GetMarkers(); if (TimelineMarker *existing = markers->GetMarkerAtTime(GetTime())) { // We already have a marker here, so pop open the edit dialog @@ -661,8 +676,8 @@ void TimeBasedWidget::ToggleShowAll() void TimeBasedWidget::GoToIn() { if (GetConnectedNode()) { - if (GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); + if (GetConnectedNode()->GetWorkArea()->enabled()) { + SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); } else { GoToStart(); } @@ -672,8 +687,8 @@ void TimeBasedWidget::GoToIn() void TimeBasedWidget::GoToOut() { if (GetConnectedNode()) { - if (GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->out()); + if (GetConnectedNode()->GetWorkArea()->enabled()) { + SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->out()); } else { GoToEnd(); } @@ -750,7 +765,7 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration // Snap to clip markers too if (ClipBlock *clip = dynamic_cast(b)) { if (clip->connected_viewer()) { - TimelineMarkerList *markers = clip->connected_viewer()->GetTimelinePoints()->markers(); + TimelineMarkerList *markers = clip->connected_viewer()->GetMarkers(); for (auto jt=markers->cbegin(); jt!=markers->cend(); jt++) { TimelineMarker *marker = *jt; @@ -768,8 +783,8 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration } } - if ((snap_points & kSnapToMarkers) && ruler()->GetTimelinePoints()) { - for (auto it=ruler()->GetTimelinePoints()->markers()->cbegin(); it!=ruler()->GetTimelinePoints()->markers()->cend(); it++) { + if ((snap_points & kSnapToMarkers) && ruler()->GetMarkers()) { + for (auto it=ruler()->GetMarkers()->cbegin(); it!=ruler()->GetMarkers()->cend(); it++) { TimelineMarker* m = *it; // Ignore selected markers @@ -787,9 +802,9 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration } } - if ((snap_points & kSnapToWorkarea) && ruler()->GetTimelinePoints()) { - const rational &workarea_in = ruler()->GetTimelinePoints()->workarea()->in(); - const rational &workarea_out = ruler()->GetTimelinePoints()->workarea()->out(); + if ((snap_points & kSnapToWorkarea) && ruler()->GetWorkArea()) { + const rational &workarea_in = ruler()->GetWorkArea()->in(); + const rational &workarea_out = ruler()->GetWorkArea()->out(); AttemptSnap(potential_snaps, screen_pt, TimeToScene(workarea_in), start_times, workarea_in); AttemptSnap(potential_snaps, screen_pt, TimeToScene(workarea_out), start_times, workarea_out); diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 82924160f..509e8f8b9 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -50,6 +50,11 @@ public: void ConnectViewerNode(ViewerOutput *node); + TimelineWorkArea *GetConnectedWorkArea() const { return workarea_; } + TimelineMarkerList *GetConnectedMarkers() const { return markers_; } + void ConnectWorkArea(TimelineWorkArea *workarea); + void ConnectMarkers(TimelineMarkerList *markers); + void SetScaleAndCenterOnPlayhead(const double& scale); TimeRuler* ruler() const; @@ -130,6 +135,9 @@ protected: virtual void ConnectedNodeChangeEvent(ViewerOutput*){} + virtual void ConnectedWorkAreaChangeEvent(TimelineWorkArea *){} + virtual void ConnectedMarkersChangeEvent(TimelineMarkerList *){} + virtual void ConnectNodeEvent(ViewerOutput*){} virtual void DisconnectNodeEvent(ViewerOutput*){} @@ -217,6 +225,9 @@ private: double scrollbar_start_scale_; bool scrollbar_top_handle_; + TimelineWorkArea *workarea_; + TimelineMarkerList *markers_; + private slots: void UpdateMaximumScroll(); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index aeb1a0678..caffc39de 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -638,7 +638,7 @@ void TimelineWidget::PasteInsert() void TimelineWidget::DeleteInToOut(bool ripple) { if (!GetConnectedNode() - || !GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { + || !GetConnectedNode()->GetWorkArea()->enabled()) { return; } @@ -648,8 +648,8 @@ void TimelineWidget::DeleteInToOut(bool ripple) command->add_child(new TimelineRippleRemoveAreaCommand( sequence(), - GetConnectedNode()->GetTimelinePoints()->workarea()->in(), - GetConnectedNode()->GetTimelinePoints()->workarea()->out())); + GetConnectedNode()->GetWorkArea()->in(), + GetConnectedNode()->GetWorkArea()->out())); } else { QVector unlocked_tracks = sequence()->GetUnlockedTracks(); @@ -657,7 +657,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) foreach (Track* track, unlocked_tracks) { GapBlock* gap = new GapBlock(); - gap->set_length_and_media_out(GetConnectedNode()->GetTimelinePoints()->workarea()->length()); + gap->set_length_and_media_out(GetConnectedNode()->GetWorkArea()->length()); command->add_child(new NodeAddCommand(static_cast(track->parent()), gap)); @@ -665,17 +665,17 @@ void TimelineWidget::DeleteInToOut(bool ripple) command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track->type()), track->Index(), gap, - GetConnectedNode()->GetTimelinePoints()->workarea()->in())); + GetConnectedNode()->GetWorkArea()->in())); } } // Clear workarea after this command->add_child(new WorkareaSetEnabledCommand(GetConnectedNode()->project(), - GetConnectedNode()->GetTimelinePoints(), + GetConnectedNode()->GetWorkArea(), false)); if (ripple) { - SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); + SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); } Core::instance()->undo_stack()->push(command); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index bc1a5590a..3914e137f 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -231,7 +231,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData rational footage_duration; rational ghost_in; - TimelineWorkArea* wk = footage->GetTimelinePoints()->workarea(); + TimelineWorkArea* wk = footage->GetWorkArea(); if (wk->enabled()) { footage_duration = wk->length(); ghost_in = wk->in(); diff --git a/app/widget/timelinewidget/undo/timelineundoworkarea.h b/app/widget/timelinewidget/undo/timelineundoworkarea.h index b953fafa7..f83601747 100644 --- a/app/widget/timelinewidget/undo/timelineundoworkarea.h +++ b/app/widget/timelinewidget/undo/timelineundoworkarea.h @@ -22,16 +22,15 @@ #define TIMELINEUNDOWORKAREA_H #include "node/project/project.h" -#include "timeline/timelinepoints.h" namespace olive { class WorkareaSetEnabledCommand : public UndoCommand { public: - WorkareaSetEnabledCommand(Project *project, TimelinePoints* points, bool enabled) : + WorkareaSetEnabledCommand(Project *project, TimelineWorkArea* points, bool enabled) : project_(project), points_(points), - old_enabled_(points_->workarea()->enabled()), + old_enabled_(points_->enabled()), new_enabled_(enabled) { } @@ -44,18 +43,18 @@ public: protected: virtual void redo() override { - points_->workarea()->set_enabled(new_enabled_); + points_->set_enabled(new_enabled_); } virtual void undo() override { - points_->workarea()->set_enabled(old_enabled_); + points_->set_enabled(old_enabled_); } private: Project* project_; - TimelinePoints* points_; + TimelineWorkArea* points_; bool old_enabled_; diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index d3ee50c04..7c76ba37a 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -547,7 +547,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } } - TimelineMarkerList *marker_list = clip->connected_viewer()->GetTimelinePoints()->markers(); + TimelineMarkerList *marker_list = clip->connected_viewer()->GetMarkers(); if (!marker_list->empty()) { clip_marker_rects_.clear(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 45cfb9717..5f130485f 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -41,7 +41,8 @@ namespace olive { SeekableWidget::SeekableWidget(QWidget* parent) : super(parent), - timeline_points_(nullptr), + markers_(nullptr), + workarea_(nullptr), dragging_(false), ignore_next_focus_out_(false), selection_manager_(this), @@ -63,26 +64,41 @@ SeekableWidget::SeekableWidget(QWidget* parent) : selection_manager_.SetSnapMask(TimeBasedWidget::kSnapAll); } -void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) +void SeekableWidget::SetMarkers(TimelineMarkerList *markers) { - if (timeline_points_) { + if (markers_) { selection_manager_.ClearSelection(); - disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); - disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); - disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); - disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); - disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); + disconnect(markers_, &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); + disconnect(markers_, &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); + disconnect(markers_, &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); } - timeline_points_ = points; + markers_ = markers; - if (timeline_points_) { - connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); - connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); + if (markers_) { + connect(markers_, &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); + connect(markers_, &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); + connect(markers_, &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); + } + + viewport()->update(); +} + +void SeekableWidget::SetWorkArea(TimelineWorkArea *workarea) +{ + if (workarea_) { + selection_manager_.ClearSelection(); + + disconnect(workarea_, &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); + disconnect(workarea_, &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); + } + + workarea_ = workarea; + + if (workarea_) { + connect(workarea_, &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); + connect(workarea_, &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); } viewport()->update(); @@ -139,11 +155,11 @@ bool SeekableWidget::PasteMarkers() m->set_time(m->time().in() - min); - if (TimelineMarker *existing = timeline_points_->markers()->GetMarkerAtTime(m->time().in())) { + if (TimelineMarker *existing = markers_->GetMarkerAtTime(m->time().in())) { command->add_child(new MarkerRemoveCommand(existing)); } - command->add_child(new MarkerAddCommand(timeline_points_->markers(), m)); + command->add_child(new MarkerAddCommand(markers_, m)); } Core::instance()->undo_stack()->push(command); @@ -187,7 +203,7 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) } else { SeekToScenePoint(scene.x()); } - } else if (timeline_points_) { + } else { // Look for resize points if (FindResizeHandle(event)) { setCursor(Qt::SizeHorCursor); @@ -238,6 +254,59 @@ void SeekableWidget::focusOutEvent(QFocusEvent *event) } } +void SeekableWidget::DrawMarkers(QPainter *p, int marker_bottom) +{ + selection_manager_.ClearDrawnObjects(); + + // Draw markers + if (markers_ && !markers_->empty() && marker_bottom > 0) { + int lim_left = GetLeftLimit(); + int lim_right = GetRightLimit(); + + for (auto it=markers_->cbegin(); it!=markers_->cend(); it++) { + TimelineMarker* marker = *it; + + int marker_right = TimeToScene(marker->time().out()); + if (marker_right < lim_left) { + continue; + } + + int marker_left = TimeToScene(marker->time().in()); + if (marker_left >= lim_right) { + break; + } + + QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker)); + marker_top_ = marker_rect.top(); + selection_manager_.DeclareDrawnObject(marker, marker_rect); + } + } + + marker_bottom_ = marker_bottom; +} + +void SeekableWidget::DrawWorkArea(QPainter *p) +{ + // Draw in/out workarea + if (workarea_ && workarea_->enabled()) { + int lim_left = GetLeftLimit(); + int lim_right = GetRightLimit(); + + int workarea_left = qMax(qreal(lim_left), TimeToScene(workarea_->in())); + int workarea_right; + + if (workarea_->out() == TimelineWorkArea::kResetOut) { + workarea_right = lim_right; + } else { + workarea_right = qMin(qreal(lim_right), TimeToScene(workarea_->out())); + } + + QColor translucent_highlight = palette().highlight().color(); + translucent_highlight.setAlpha(96); + p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), translucent_highlight); + } +} + void SeekableWidget::DeselectAllMarkers() { selection_manager_.ClearSelection(); @@ -309,57 +378,6 @@ void SeekableWidget::SelectionManagerDeselectEvent(void *obj) viewport()->update(); } -void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) -{ - if (!GetTimelinePoints()) { - return; - } - - int lim_left = GetScroll(); - int lim_right = lim_left + width(); - - selection_manager_.ClearDrawnObjects(); - - // Draw in/out workarea - if (GetTimelinePoints()->workarea()->enabled()) { - int workarea_left = qMax(qreal(lim_left), TimeToScene(GetTimelinePoints()->workarea()->in())); - int workarea_right; - - if (GetTimelinePoints()->workarea()->out() == TimelineWorkArea::kResetOut) { - workarea_right = lim_right; - } else { - workarea_right = qMin(qreal(lim_right), TimeToScene(GetTimelinePoints()->workarea()->out())); - } - - QColor translucent_highlight = palette().highlight().color(); - translucent_highlight.setAlpha(96); - p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), translucent_highlight); - } - - // Draw markers - if (marker_bottom > 0 && !GetTimelinePoints()->markers()->empty()) { - for (auto it=GetTimelinePoints()->markers()->cbegin(); it!=GetTimelinePoints()->markers()->cend(); it++) { - TimelineMarker* marker = *it; - - int marker_right = TimeToScene(marker->time().out()); - if (marker_right < lim_left) { - continue; - } - - int marker_left = TimeToScene(marker->time().in()); - if (marker_left >= lim_right) { - break; - } - - QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker)); - marker_top_ = marker_rect.top(); - selection_manager_.DeclareDrawnObject(marker, marker_rect); - } - } - - marker_bottom_ = marker_bottom; -} - void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) { int half_width = playhead_width_ / 2; @@ -389,6 +407,16 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) p->setRenderHint(QPainter::Antialiasing, false); } +int SeekableWidget::GetLeftLimit() const +{ + return GetScroll(); +} + +int SeekableWidget::GetRightLimit() const +{ + return GetLeftLimit() + width(); +} + bool SeekableWidget::ShowContextMenu(const QPoint &p) { if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().empty()) { @@ -427,32 +455,38 @@ bool SeekableWidget::FindResizeHandle(QMouseEvent *event) rational max = SceneToTimeNoGrid(scene.x() + border); // Test for workarea - if (timeline_points_->workarea()->in() >= min && timeline_points_->workarea()->in() < max) { - resize_mode_ = kResizeIn; - } else if (timeline_points_->workarea()->out() >= min && timeline_points_->workarea()->out() < max) { - resize_mode_ = kResizeOut; + if (workarea_) { + if (workarea_->in() >= min && workarea_->in() < max) { + resize_mode_ = kResizeIn; + } else if (workarea_->out() >= min && workarea_->out() < max) { + resize_mode_ = kResizeOut; + } } if (resize_mode_ != kResizeNone) { - resize_item_ = timeline_points_->workarea(); - resize_item_range_ = timeline_points_->workarea()->range(); - resize_snap_mask_ = TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToWorkarea; + if (workarea_) { + resize_item_ = workarea_; + resize_item_range_ = workarea_->range(); + resize_snap_mask_ = TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToWorkarea; + } } else if (event->pos().y() >= marker_top_ && event->pos().y() < marker_bottom_) { - // Check for markers - for (auto it=timeline_points_->markers()->cbegin(); it!=timeline_points_->markers()->cend(); it++) { - TimelineMarker *m = *it; - if (m->time().in() != m->time().out()) { - if (m->time().in() >= min && m->time().in() < max) { - resize_mode_ = kResizeIn; - } else if (m->time().out() >= min && m->time().out() < max) { - resize_mode_ = kResizeOut; - } + if (markers_) { + // Check for markers + for (auto it=markers_->cbegin(); it!=markers_->cend(); it++) { + TimelineMarker *m = *it; + if (m->time().in() != m->time().out()) { + if (m->time().in() >= min && m->time().in() < max) { + resize_mode_ = kResizeIn; + } 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(); - resize_snap_mask_ = TimeBasedWidget::kSnapAll; - break; + if (resize_mode_ != kResizeNone) { + resize_item_ = m; + resize_item_range_ = m->time(); + resize_snap_mask_ = TimeBasedWidget::kSnapAll; + break; + } } } } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index dc28e9dbe..d29686f8f 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -25,7 +25,6 @@ #include #include "common/rational.h" -#include "timeline/timelinepoints.h" #include "widget/menu/menu.h" #include "widget/timebased/timebasedviewselectionmanager.h" @@ -42,8 +41,11 @@ public: return horizontalScrollBar()->value(); } - TimelinePoints* GetTimelinePoints() const { return timeline_points_; } - void ConnectTimelinePoints(TimelinePoints* points); + TimelineMarkerList *GetMarkers() const { return markers_; } + TimelineWorkArea *GetWorkArea() const { return workarea_; } + + void SetMarkers(TimelineMarkerList *markers); + void SetWorkArea(TimelineWorkArea *workarea); bool IsDraggingPlayhead() const { @@ -84,7 +86,8 @@ protected: virtual void focusOutEvent(QFocusEvent *event) override; - void DrawTimelinePoints(QPainter *p, int marker_bottom = 0); + void DrawMarkers(QPainter *p, int marker_bottom = 0); + void DrawWorkArea(QPainter *p); void DrawPlayhead(QPainter* p, int x, int y); @@ -96,6 +99,9 @@ protected: return playhead_width_; } + int GetLeftLimit() const; + int GetRightLimit() const; + protected slots: virtual bool ShowContextMenu(const QPoint &p); @@ -112,7 +118,8 @@ private: void CommitResizeHandle(); - TimelinePoints* timeline_points_; + TimelineMarkerList* markers_; + TimelineWorkArea* workarea_; int text_height_; diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 642e160df..39d356006 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -103,9 +103,8 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) // Draw timeline points if connected int marker_height = TimelineMarker::GetMarkerHeight(p->fontMetrics()); - if (GetTimelinePoints()) { - DrawTimelinePoints(p, marker_height); - } + DrawMarkers(p, marker_height); + DrawWorkArea(p); double width_of_frame = timebase_dbl() * GetScale(); double width_of_second = 0; diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 92dc7e500..073edc0d8 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -82,7 +82,8 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect) } // Draw in/out points - DrawTimelinePoints(p); + DrawWorkArea(p); + DrawMarkers(p); // Draw waveform p->setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f9bf5a8e0..82c3a2459 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -233,7 +233,6 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) UpdateWaveformViewFromMode(); waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache()); - waveform_view_->ConnectTimelinePoints(GetConnectedNode()->GetTimelinePoints()); UpdateRendererVideoParameters(); UpdateRendererAudioParameters(); @@ -270,7 +269,6 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) } waveform_view_->SetViewer(nullptr); - waveform_view_->ConnectTimelinePoints(nullptr); // Queue an UpdateStack so that when it runs, the viewer node will be fully disconnected QMetaObject::invokeMethod(this, &ViewerWidget::UpdateWaveformViewFromMode, Qt::QueuedConnection); @@ -284,6 +282,16 @@ void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n) display_widget_->SetSubtitleTracks(dynamic_cast(n)); } +void ViewerWidget::ConnectedWorkAreaChangeEvent(TimelineWorkArea *workarea) +{ + waveform_view_->SetWorkArea(workarea); +} + +void ViewerWidget::ConnectedMarkersChangeEvent(TimelineMarkerList *markers) +{ + waveform_view_->SetMarkers(markers); +} + void ViewerWidget::ScaleChangedEvent(const double &s) { super::ScaleChangedEvent(s); @@ -379,8 +387,8 @@ void ViewerWidget::CacheEntireSequence() void ViewerWidget::CacheSequenceInOut() { - if (GetConnectedNode() && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - auto_cacher_.ForceCacheRange(GetConnectedNode()->GetTimelinePoints()->workarea()->range()); + if (GetConnectedNode() && GetConnectedNode()->GetWorkArea()->enabled()) { + auto_cacher_.ForceCacheRange(GetConnectedNode()->GetWorkArea()->range()); } else { QMessageBox::warning(this, tr("Error"), @@ -1314,9 +1322,9 @@ void ViewerWidget::Play(bool in_to_out_only) { if (in_to_out_only) { if (GetConnectedNode() - && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { + && GetConnectedNode()->GetWorkArea()->enabled()) { // Jump to in point - SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); + SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); } else { in_to_out_only = false; } @@ -1444,11 +1452,11 @@ void ViewerWidget::PlaybackTimerUpdate() min_time = recording_range_.in(); max_time = recording_range_.out(); - } else if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { + } else if (play_in_to_out_only_ && GetConnectedNode()->GetWorkArea()->enabled()) { // If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea - min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in(); - max_time = GetConnectedNode()->GetTimelinePoints()->workarea()->out(); + min_time = GetConnectedNode()->GetWorkArea()->in(); + max_time = GetConnectedNode()->GetWorkArea()->out(); } else { diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index d38ef5316..f1aefcb85 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -163,6 +163,8 @@ protected: virtual void ConnectNodeEvent(ViewerOutput *) override; virtual void DisconnectNodeEvent(ViewerOutput *) override; virtual void ConnectedNodeChangeEvent(ViewerOutput *) override; + virtual void ConnectedWorkAreaChangeEvent(TimelineWorkArea *) override; + virtual void ConnectedMarkersChangeEvent(TimelineMarkerList *) override; virtual void ScaleChangedEvent(const double& s) override; From 89927c71ff2c6a565c8f72b2431a1003883bd8a8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Jun 2022 23:40:05 -0700 Subject: [PATCH 33/40] implemented reveal in footage viewer option Fixes #1958 --- app/node/block/clip/clip.cpp | 5 +++++ app/node/block/clip/clip.h | 2 ++ app/panel/footageviewer/footageviewer.cpp | 13 +++++++++---- app/panel/footageviewer/footageviewer.h | 8 ++++++++ app/widget/timelinewidget/timelinewidget.cpp | 15 +++++++++++++++ app/widget/timelinewidget/timelinewidget.h | 2 ++ app/widget/viewer/footageviewer.cpp | 16 ++++++++++++++++ app/widget/viewer/footageviewer.h | 5 +++++ app/window/mainwindow/mainwindow.cpp | 16 ++++++++++++++++ app/window/mainwindow/mainwindow.h | 1 + 10 files changed, 79 insertions(+), 4 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 080850c45..f8bdea614 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -284,4 +284,9 @@ void ClipBlock::Retranslate() SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); } +TimeRange ClipBlock::media_range() const +{ + return InputTimeAdjustment(kBufferIn, -1, range()); +} + } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index fce20dbbd..6541a8710 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -119,6 +119,8 @@ public: return connected_viewer_; } + TimeRange media_range() const; + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; diff --git a/app/panel/footageviewer/footageviewer.cpp b/app/panel/footageviewer/footageviewer.cpp index ebb86a007..d005f11cf 100644 --- a/app/panel/footageviewer/footageviewer.cpp +++ b/app/panel/footageviewer/footageviewer.cpp @@ -20,12 +20,12 @@ #include "footageviewer.h" -#include "widget/viewer/footageviewer.h" - namespace olive { +#define super ViewerPanelBase + FootageViewerPanel::FootageViewerPanel(QWidget *parent) : - ViewerPanelBase(QStringLiteral("FootageViewerPanel"), parent) + super(QStringLiteral("FootageViewerPanel"), parent) { // Set ViewerWidget as the central widget FootageViewerWidget* fvw = new FootageViewerWidget(); @@ -38,6 +38,11 @@ FootageViewerPanel::FootageViewerPanel(QWidget *parent) : SetShowAndRaiseOnConnect(); } +void FootageViewerPanel::OverrideWorkArea(const TimeRange &r) +{ + GetFootageViewerWidget()->OverrideWorkArea(r); +} + QVector FootageViewerPanel::GetSelectedFootage() const { QVector list; @@ -51,7 +56,7 @@ QVector FootageViewerPanel::GetSelectedFootage() const void FootageViewerPanel::Retranslate() { - ViewerPanelBase::Retranslate(); + super::Retranslate(); SetTitle(tr("Footage Viewer")); } diff --git a/app/panel/footageviewer/footageviewer.h b/app/panel/footageviewer/footageviewer.h index 2b07cc233..9f546d036 100644 --- a/app/panel/footageviewer/footageviewer.h +++ b/app/panel/footageviewer/footageviewer.h @@ -25,6 +25,7 @@ #include "panel/viewer/viewerbase.h" #include "panel/project/footagemanagementpanel.h" +#include "widget/viewer/footageviewer.h" namespace olive { @@ -36,6 +37,13 @@ class FootageViewerPanel : public ViewerPanelBase, public FootageManagementPanel public: FootageViewerPanel(QWidget* parent); + void OverrideWorkArea(const TimeRange &r); + + FootageViewerWidget *GetFootageViewerWidget() const + { + return static_cast(GetTimeBasedWidget()); + } + virtual QVector GetSelectedFootage() const override; protected: diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index caffc39de..c729a6266 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1073,6 +1073,11 @@ void TimelineWidget::ShowContextMenu() if (ClipBlock *clip = dynamic_cast(selected.first())) { if (clip->connected_viewer()) { + QAction *reveal_in_footage_viewer = menu.addAction(tr("Reveal in Footage Viewer")); + reveal_in_footage_viewer->setData(reinterpret_cast(clip->connected_viewer())); + reveal_in_footage_viewer->setProperty("range", QVariant::fromValue(clip->media_range())); + connect(reveal_in_footage_viewer, &QAction::triggered, this, &TimelineWidget::RevealInFootageViewer); + QAction *reveal_in_project = menu.addAction(tr("Reveal in Project")); reveal_in_project->setData(reinterpret_cast(clip->connected_viewer())); connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject); @@ -1203,6 +1208,16 @@ void TimelineWidget::SignalBlockSelectionChange() signal_block_change_timer_->start(); } +void TimelineWidget::RevealInFootageViewer() +{ + QAction *a = static_cast(sender()); + + ViewerOutput *item_to_reveal = reinterpret_cast(a->data().value()); + TimeRange r = a->property("range").value(); + + emit RevealViewerInFootageViewer(item_to_reveal, r); +} + void TimelineWidget::RevealInProject() { QAction *a = static_cast(sender()); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index c7a38dfaa..532b2b600 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -277,6 +277,7 @@ signals: void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); + void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); void RevealViewerInProject(ViewerOutput *r); protected: @@ -425,6 +426,7 @@ private slots: void SignalBlockSelectionChange(); + void RevealInFootageViewer(); void RevealInProject(); void RenameSelectedBlocks(); diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 9ede7b066..16c9b2d4c 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -38,6 +38,22 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : controls_->SetAudioVideoDragButtonsVisible(true); connect(controls_, &PlaybackControls::VideoPressed, this, &FootageViewerWidget::StartVideoDrag); connect(controls_, &PlaybackControls::AudioPressed, this, &FootageViewerWidget::StartAudioDrag); + + override_workarea_ = new TimelineWorkArea(this); +} + +void FootageViewerWidget::OverrideWorkArea(const TimeRange &r) +{ + override_workarea_->set_enabled(true); + override_workarea_->set_range(r); + this->ConnectWorkArea(override_workarea_); +} + +void FootageViewerWidget::ResetWorkArea() +{ + if (GetConnectedWorkArea() == override_workarea_) { + this->ConnectWorkArea(GetConnectedNode() ? GetConnectedNode()->GetWorkArea() : nullptr); + } } void FootageViewerWidget::ConnectNodeEvent(ViewerOutput *n) diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index e9ef774f3..de98866c8 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -32,6 +32,9 @@ class FootageViewerWidget : public ViewerWidget public: FootageViewerWidget(QWidget* parent = nullptr); + void OverrideWorkArea(const TimeRange &r); + void ResetWorkArea(); + protected: virtual void ConnectNodeEvent(ViewerOutput *) override; @@ -42,6 +45,8 @@ private: QHash cached_timestamps_; + TimelineWorkArea *override_workarea_; + private slots: void StartFootageDrag(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index f1b238f6e..e7f017a69 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -33,6 +33,7 @@ #include "dialog/about/about.h" #include "mainmenu.h" #include "mainstatusbar.h" +#include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -489,6 +490,20 @@ void MainWindow::RevealViewerInProject(ViewerOutput *r) } } +void MainWindow::RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range) +{ + footage_viewer_panel_->ConnectViewerNode(r); + + auto command = new MultiUndoCommand(); + if (!r->GetWorkArea()->enabled()) { + command->add_child(new WorkareaSetEnabledCommand(r->project(), r->GetWorkArea(), true)); + } + command->add_child(new WorkareaSetRangeCommand(r->GetWorkArea(), range)); + Core::instance()->undo_stack()->push(command); + + footage_viewer_panel_->SetTime(range.in()); +} + #ifdef Q_OS_LINUX void MainWindow::ShowNouveauWarning() { @@ -568,6 +583,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture); connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged); connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject); + connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 53d6bb6c0..20498b684 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -196,6 +196,7 @@ private slots: void ShowWelcomeDialog(); void RevealViewerInProject(ViewerOutput *r); + void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); }; From c34066952f5093870e465ef903845ee824572148 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 09:39:08 -0700 Subject: [PATCH 34/40] timelineview: update rect on height change Fixes #1950 --- app/node/output/track/tracklist.cpp | 3 +++ app/node/output/track/tracklist.h | 2 ++ app/widget/timelinewidget/view/timelineview.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index bd8b7e813..3149d973b 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -81,6 +81,9 @@ void TrackList::TrackConnected(Node *node, int element) UpdateTrackIndexesFrom(cache_index); connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); + connect(track, &Track::TrackHeightChangedInPixels, this, [this](int height){ + emit TrackHeightChanged(static_cast(sender()), height); + }); track->set_type(type_); track->set_sequence(parent()); diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h index 6096d0858..bfa76a30e 100644 --- a/app/node/output/track/tracklist.h +++ b/app/node/output/track/tracklist.h @@ -101,6 +101,8 @@ signals: void TrackRemoved(Track* track); + void TrackHeightChanged(Track *track, int height); + private: void UpdateTrackIndexesFrom(int index); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 7c76ba37a..35b40259e 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -707,12 +707,14 @@ void TimelineView::ConnectTrackList(TrackList *list) { if (connected_track_list_) { disconnect(connected_track_list_, &TrackList::TrackListChanged, this, &TimelineView::TrackListChanged); + disconnect(connected_track_list_, &TrackList::TrackHeightChanged, this, &TimelineView::TrackListChanged); } connected_track_list_ = list; if (connected_track_list_) { connect(connected_track_list_, &TrackList::TrackListChanged, this, &TimelineView::TrackListChanged); + connect(connected_track_list_, &TrackList::TrackHeightChanged, this, &TimelineView::TrackListChanged); } } From 9b5e0f393d9aeb022d38b6ccfe7f12e77aa05e5f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 09:53:56 -0700 Subject: [PATCH 35/40] crashhandler: handle ssl errors --- app/crashhandler/crashhandler.cpp | 17 +++++++++++++++++ app/crashhandler/crashhandler.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/app/crashhandler/crashhandler.cpp b/app/crashhandler/crashhandler.cpp index a8d9368bb..b686d6f58 100644 --- a/app/crashhandler/crashhandler.cpp +++ b/app/crashhandler/crashhandler.cpp @@ -161,6 +161,22 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) } } +void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList &errors) +{ + QStringList errors; + for (const QSslError &err : errors) { + errors.append(err.errorString()); + } + + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("SSL Error")); + b.setText(tr("Encountered the following SSL errors:\n\n%1").arg(errors.join('\n'))); + b.addButton(QMessageBox::Ok); + b.exec(); +} + void CrashHandlerDialog::AttemptToFindReport() { // If we found it, use it, otherwise wait a second and try again @@ -198,6 +214,7 @@ void CrashHandlerDialog::SendErrorReport() QNetworkAccessManager* manager = new QNetworkAccessManager(); connect(manager, &QNetworkAccessManager::finished, this, &CrashHandlerDialog::ReplyFinished); + connect(manager, &QNetworkAccessManager::sslErrors, this, &CrashHandlerDialog::HandleSslErrors); QNetworkRequest request; request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); diff --git a/app/crashhandler/crashhandler.h b/app/crashhandler/crashhandler.h index d4e37d583..d377061e2 100644 --- a/app/crashhandler/crashhandler.h +++ b/app/crashhandler/crashhandler.h @@ -65,6 +65,8 @@ protected: private slots: void ReplyFinished(QNetworkReply *reply); + void HandleSslErrors(QNetworkReply *reply, const QList &errors); + void AttemptToFindReport(); void ReadProcessHasData(); From 7785c0d66a49f3520309226d95eaf99c4de3f3f1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 10:05:43 -0700 Subject: [PATCH 36/40] crashhandler: fix duplicate variable name --- app/crashhandler/crashhandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/crashhandler/crashhandler.cpp b/app/crashhandler/crashhandler.cpp index b686d6f58..5fe8cfb75 100644 --- a/app/crashhandler/crashhandler.cpp +++ b/app/crashhandler/crashhandler.cpp @@ -161,10 +161,10 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) } } -void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList &errors) +void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList &se) { QStringList errors; - for (const QSslError &err : errors) { + for (const QSslError &err : se) { errors.append(err.errorString()); } From ac207e8fe793f1102039626460f0e9a0588ef05a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 12:09:10 -0700 Subject: [PATCH 37/40] timeline: implement add default transition Fixes #1939 --- app/config/config.cpp | 4 + app/panel/timeline/timeline.h | 5 + app/widget/menu/menushared.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 16 +++ app/widget/timelinewidget/timelinewidget.h | 2 + .../undo/timelineundogeneral.cpp | 132 +++++++++++++++++- .../timelinewidget/undo/timelineundogeneral.h | 55 ++++++++ 7 files changed, 213 insertions(+), 3 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 43668c467..4b62bbacc 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -104,6 +104,10 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); + SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); + SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); + SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed); diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index cf596a724..da0418e46 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -86,6 +86,11 @@ public: virtual void MoveOutToPlayhead() override; + void AddDefaultTransitionsToSelected() + { + timeline_widget()->AddDefaultTransitionsToSelected(); + } + void ShowSpeedDurationDialogForSelectedClips() { timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 6dc566e31..431e72f72 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -291,7 +291,7 @@ void MenuShared::NestTriggered() void MenuShared::DefaultTransitionTriggered() { - qDebug() << "FIXME: Stub"; + PanelManager::instance()->MostRecentlyFocused()->AddDefaultTransitionsToSelected(); } void MenuShared::TimecodeDisplayTriggered() diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index c729a6266..3dc750a86 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -568,6 +568,22 @@ void TimelineWidget::ToggleLinksOnSelected() Core::instance()->undo_stack()->push(new NodeLinkManyCommand(blocks, link)); } +void TimelineWidget::AddDefaultTransitionsToSelected() +{ + QVector blocks; + + foreach (Block* item, GetSelectedBlocks()) { + // Only clips can be linked + if (ClipBlock *clip = dynamic_cast(item)) { + blocks.append(clip); + } + } + + if (!blocks.isEmpty()) { + Core::instance()->undo_stack()->push(new TimelineAddDefaultTransitionCommand(blocks, timebase())); + } +} + bool TimelineWidget::CopySelected(bool cut) { if (super::CopySelected(cut)) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 532b2b600..58846f53a 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -79,6 +79,8 @@ public: void ToggleLinksOnSelected(); + void AddDefaultTransitionsToSelected(); + virtual bool CopySelected(bool cut) override; virtual bool Paste() override; diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index c90d8b078..d774d47e6 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -22,9 +22,11 @@ #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" +#include "node/factory.h" #include "node/math/math/math.h" #include "node/math/merge/merge.h" #include "timelineundocommon.h" +#include "widget/timelinewidget/undo/timelineundotrack.h" namespace olive { @@ -287,8 +289,8 @@ void TrackListInsertGaps::prepare() QVector blocks_to_append_gap_to; QVector tracks_to_append_gap_to; - foreach (Track* track, working_tracks_) { - foreach (Block* b, track->Blocks()) { + for (Track* track : qAsConst(working_tracks_)) { + for (Block* b : track->Blocks()) { if (dynamic_cast(b) && b->in() <= point_ && b->out() >= point_) { // Found a gap at the location gaps_to_extend_.append(b); @@ -542,4 +544,130 @@ void TimelineRemoveTrackCommand::undo() remove_command_->undo_now(); } +void TimelineAddDefaultTransitionCommand::prepare() +{ + for (auto it=clips_.cbegin(); it!=clips_.cend(); it++) { + ClipBlock *c = *it; + + // Handle in transition + if (clips_.contains(static_cast(c->previous()))) { + // Do nothing, assume this will be handled by a dual transition from that clip + } else if (dynamic_cast(c->previous()) || !c->previous()) { + // Create in transition + AddTransition(c, kIn); + } + + // Handle out transition + if (clips_.contains(static_cast(c->next()))) { + AddTransition(c, kOutDual); + } else if (dynamic_cast(c->next()) || !c->next()) { + // Create out transition + AddTransition(c, kOut); + } + } +} + +void TimelineAddDefaultTransitionCommand::AddTransition(ClipBlock *c, CreateTransitionMode mode) +{ + if (Track *t = c->track()) { + Node *p = nullptr; + if (t->type() == Track::kVideo) { + p = NodeFactory::CreateFromID(OLIVE_CONFIG("DefaultVideoTransition").toString()); + } else if (t->type() == Track::kAudio) { + p = NodeFactory::CreateFromID(OLIVE_CONFIG("DefaultAudioTransition").toString()); + } + + rational transition_length = OLIVE_CONFIG("DefaultTransitionLength").value(); + + // Resize original clip + switch (mode) { + case kIn: + ValidateTransitionLength(c, transition_length); + + if (transition_length > 0) { + AdjustClipLength(c, transition_length, false); + } + break; + case kOut: + ValidateTransitionLength(c, transition_length); + + if (transition_length > 0) { + AdjustClipLength(c, transition_length, true); + } + break; + case kOutDual: + { + rational half_length = transition_length / 2; + + ValidateTransitionLength(static_cast(c->next()), half_length); + ValidateTransitionLength(c, half_length); + + transition_length = half_length * 2; + + if (transition_length > 0) { + AdjustClipLength(static_cast(c->next()), half_length, false); + AdjustClipLength(c, half_length, true); + } + break; + } + } + + if (transition_length > 0) { + if (TransitionBlock *transition = dynamic_cast(p)) { + transition->set_length_and_media_out(transition_length); + + // Add transition + commands_.append(new NodeAddCommand(c->parent(), transition)); + + // Insert block + Block *insert_after; + switch (mode) { + case kIn: + insert_after = c->previous(); + break; + case kOut: + case kOutDual: + insert_after = c; + break; + } + commands_.append(new TrackInsertBlockAfterCommand(c->track(), transition, insert_after)); + + // Connect + switch (mode) { + case kIn: + commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kInBlockInput))); + break; + case kOutDual: + commands_.append(new NodeEdgeAddCommand(c->next(), NodeInput(transition, TransitionBlock::kInBlockInput))); + /* fall through */ + case kOut: + commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kOutBlockInput))); + break; + } + } + } + } +} + +void TimelineAddDefaultTransitionCommand::AdjustClipLength(ClipBlock *c, const rational &transition_length, bool out) +{ + rational cur_len = lengths_.value(c, c->length()); + rational new_len = cur_len - transition_length; + if (out) { + commands_.append(new BlockResizeCommand(c, new_len)); + } else { + commands_.append(new BlockResizeWithMediaInCommand(c, new_len)); + } + lengths_.insert(c, new_len); +} + +void TimelineAddDefaultTransitionCommand::ValidateTransitionLength(ClipBlock *c, rational &transition_length) +{ + rational cur_len = lengths_.value(c, c->length()); + rational half_cur_len = cur_len/2; + if (transition_length >= half_cur_len) { + transition_length = half_cur_len - timebase_; + } +} + } diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.h b/app/widget/timelinewidget/undo/timelineundogeneral.h index 55f36b7b9..50d04fe09 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.h +++ b/app/widget/timelinewidget/undo/timelineundogeneral.h @@ -358,6 +358,61 @@ private: }; +class TimelineAddDefaultTransitionCommand : public UndoCommand +{ +public: + TimelineAddDefaultTransitionCommand(const QVector &clips, const rational &timebase) : + clips_(clips), + timebase_(timebase) + {} + + virtual ~TimelineAddDefaultTransitionCommand() override + { + qDeleteAll(commands_); + } + + virtual Project* GetRelevantProject() const override + { + return clips_.empty() ? nullptr : clips_.first()->project(); + } + +protected: + virtual void prepare() override; + + virtual void redo() override + { + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { + (*it)->redo_now(); + } + } + + virtual void undo() override + { + for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) { + (*it)->undo_now(); + } + } + +private: + enum CreateTransitionMode { + kIn, + kOut, + kOutDual + }; + + void AddTransition(ClipBlock *c, CreateTransitionMode mode); + void AdjustClipLength(ClipBlock *c, const rational &transition_length, bool out); + void ValidateTransitionLength(ClipBlock *c, rational &transition_length); + + + QVector clips_; + rational timebase_; + QVector commands_; + + QHash lengths_; + +}; + } #endif // TIMELINEUNDOGENERAL_H From b5c61b68fe2778b96f30d30297762cf63e5cba43 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 13:03:43 -0700 Subject: [PATCH 38/40] timeline: minor code cleanup --- .../timelinewidget/undo/timelineundogeneral.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index d774d47e6..634188e65 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -620,16 +620,7 @@ void TimelineAddDefaultTransitionCommand::AddTransition(ClipBlock *c, CreateTran commands_.append(new NodeAddCommand(c->parent(), transition)); // Insert block - Block *insert_after; - switch (mode) { - case kIn: - insert_after = c->previous(); - break; - case kOut: - case kOutDual: - insert_after = c; - break; - } + Block *insert_after = (mode == kIn) ? c->previous() : c; commands_.append(new TrackInsertBlockAfterCommand(c->track(), transition, insert_after)); // Connect From 305fa955533d1ecc09a552f102e257554753650e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Jun 2022 13:04:25 -0700 Subject: [PATCH 39/40] ci: disable gcc build --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f1848461..857057649 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,12 +31,12 @@ jobs: fail-fast: false matrix: include: - - build-type: RelWithDebInfo - cc-compiler: gcc - cxx-compiler: g++ - compiler-name: GCC 9.3.1 - cmake-gen: Ninja - os-name: Linux (CentOS 7) + #- build-type: RelWithDebInfo + # cc-compiler: gcc + # cxx-compiler: g++ + # compiler-name: GCC 9.3.1 + # cmake-gen: Ninja + # os-name: Linux (CentOS 7) - build-type: RelWithDebInfo cc-compiler: clang cxx-compiler: clang++ From 8ff3544cb30bcba8dec53b5100ba14701b7ee28e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 1 Jul 2022 21:38:29 -0500 Subject: [PATCH 40/40] renderer: add null checks --- app/render/renderprocessor.cpp | 29 ++++++++++++++++++++++++++++- app/render/renderprocessor.h | 5 +---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 22715de65..c8296088c 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -402,6 +402,10 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) { + if (!render_ctx_) { + return; + } + if (ticket_->property("type").value() != RenderManager::kTypeVideo) { // Video cannot contribute to audio, so we do nothing here return; @@ -508,7 +512,9 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob &job) { - Q_UNUSED(range) + if (!render_ctx_) { + return; + } QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), job.GetShaderID()); @@ -562,11 +568,19 @@ void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job) { + if (!render_ctx_) { + return; + } + render_ctx_->BlitColorManaged(job, destination.get()); } void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job) { + if (!render_ctx_) { + return; + } + FramePtr frame = Frame::Create(); frame->set_video_params(destination->params()); @@ -582,8 +596,21 @@ bool RenderProcessor::CanCacheFrames() return ticket_->property("type").value() == RenderManager::kTypeVideo; } +TexturePtr RenderProcessor::CreateTexture(const VideoParams &p) +{ + if (render_ctx_) { + return render_ctx_->CreateTexture(p); + } else { + return super::CreateTexture(p); + } +} + void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) { + if (!render_ctx_) { + return; + } + ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); ColorProcessorPtr cp = ColorProcessor::Create(color_manager, input_cs, color_manager->GetReferenceColorSpace()); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 73e871880..9d7942c7a 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -58,10 +58,7 @@ protected: virtual bool CanCacheFrames() override; - virtual TexturePtr CreateTexture(const VideoParams &p) override - { - return render_ctx_->CreateTexture(p); - } + virtual TexturePtr CreateTexture(const VideoParams &p) override; virtual SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) override {