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++ 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/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index b963306ff..ac4eeb06b 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -837,7 +837,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(); } @@ -1040,7 +1040,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/common/qtutils.cpp b/app/common/qtutils.cpp index cfcaeac08..169fb25b6 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 } diff --git a/app/config/config.cpp b/app/config/config.cpp index 636b30737..be0acf49e 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/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(); + } } } diff --git a/app/crashhandler/crashhandler.cpp b/app/crashhandler/crashhandler.cpp index eac036c27..5fe8cfb75 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(); @@ -161,6 +161,22 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) } } +void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList &se) +{ + QStringList errors; + for (const QSslError &err : se) { + 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(); 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/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]); } } diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 54d195f49..069479ce4 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -289,17 +289,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); } } @@ -446,4 +446,9 @@ void ClipBlock::ConnectedToPreviewEvent() RequestInvalidatedFromConnected(); } +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 e86496104..3146eba73 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -177,6 +177,8 @@ public: virtual void ConnectedToPreviewEvent() override; + TimeRange media_range() const; + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; 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/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/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index efad82cc7..f3150514f 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -60,7 +60,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 @@ -342,7 +343,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; } @@ -351,7 +352,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; } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 156ec6a3d..ec32f1e13 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 { @@ -151,10 +152,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_; } virtual TimeRange GetVideoCacheRange() const override { @@ -238,7 +237,8 @@ private: AudioParams cached_audio_params_; - TimelinePoints *timeline_points_; + TimelineWorkArea *workarea_; + TimelineMarkerList *markers_; bool autocache_input_video_; bool autocache_input_audio_; 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 54b1c60b0..6739784e6 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -897,7 +897,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")) { @@ -1021,7 +1021,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 { @@ -1116,7 +1116,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)) { @@ -1168,27 +1168,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/audiomonitor/audiomonitor.cpp b/app/panel/audiomonitor/audiomonitor.cpp index 84297447a..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 { -AudioMonitorPanel::AudioMonitorPanel(QWidget *parent) : - PanelWidget(QStringLiteral("AudioMonitor"), parent) -{ - audio_monitor_ = new AudioMonitor(this); +#define super PanelWidget - setWidget(audio_monitor_); +AudioMonitorPanel::AudioMonitorPanel(QWidget *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/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/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..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(); @@ -109,6 +114,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/render/CMakeLists.txt b/app/render/CMakeLists.txt index b19e98d0c..a3ef82504 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -48,8 +48,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 @@ -57,6 +55,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 b7ba2937d..e68b9fd94 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(copied_viewer_node_->GetConnectedSampleOutput(), range, priority, nullptr); + return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, nullptr); } void PreviewAutoCacher::ClearSingleFrameRenders() @@ -600,7 +599,6 @@ void PreviewAutoCacher::TryRender() // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), single_frame_render_->property("time").value(), - RenderTicketPriority(single_frame_render_->property("priority").toInt()), nullptr); video_immediate_passthroughs_[watcher].append(single_frame_render_); @@ -608,8 +606,8 @@ void PreviewAutoCacher::TryRender() } if (!pause_renders_) { - // 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 while (!pending_video_jobs_.empty()) { @@ -619,7 +617,7 @@ void PreviewAutoCacher::TryRender() // Queue next frames rational t; while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { - RenderFrame(copy, t, RenderTicketPriority::kNormal, d.cache); + RenderFrame(copy, t, d.cache); emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); @@ -644,7 +642,7 @@ void PreviewAutoCacher::TryRender() // Start job if (Node *copy = copy_map_.value(d.node)) { - RenderAudio(copy, d.range, RenderTicketPriority::kNormal, d.cache); + RenderAudio(copy, d.range, d.cache); } else { qCritical() << "Failed to find node copy for audio job"; } @@ -654,13 +652,14 @@ void PreviewAutoCacher::TryRender() } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderTicketPriority priority, PlaybackCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("cache", Node::PtrToValue(cache)); watcher->setProperty("time", QVariant::fromValue(time)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); + running_video_tasks_.append(watcher); RenderManager::RenderVideoParams rvp(node, @@ -683,7 +682,6 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.AddCache(frame_cache); } - rvp.priority = priority; rvp.return_type = RenderManager::kTexture; rvp.use_cache = true; @@ -692,7 +690,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, RenderTicketPriority priority, PlaybackCache *cache) +RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); @@ -707,7 +705,6 @@ RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, R copied_viewer_node_->GetAudioParams()); rap.generate_waveforms = dynamic_cast(cache); - rap.priority = priority; rap.clamp = false; RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(rap); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index e5d95aa2f..a54c7407d 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); void ClearSingleFrameRenders(); @@ -105,9 +103,9 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, RenderTicketPriority priority, PlaybackCache *cache); + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, PlaybackCache *cache); - RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, RenderTicketPriority priority, PlaybackCache *cache); + RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache *cache); /** * @brief Process all changes to internal NodeGraph copy 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 56ea08345..573ac6d9f 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" @@ -38,20 +37,11 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; RenderManager::RenderManager(QObject *parent) : - ThreadPool(0, parent), - backend_(kOpenGL) + 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 { @@ -59,6 +49,19 @@ RenderManager::RenderManager(QObject *parent) : context_ = nullptr; 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); + decoder_clear_timer_->start(); } RenderManager::~RenderManager() @@ -67,9 +70,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(); } } @@ -95,7 +103,7 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase)); ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id)); - AddTicket(ticket, params.priority); + video_thread_->AddTicket(ticket); return ticket; } @@ -112,22 +120,131 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms) ticket->setProperty("clamp", params.clamp); ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); - AddTicket(ticket, params.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 (video_thread_->RemoveTicket(ticket)) { + return true; + } else if (audio_thread_->RemoveTicket(ticket)) { + return true; + } else { + return false; + } +} - if (ticket->IsCancelled()) { - ticket->Finish(); - return; +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()); + + qint64 min_age = QDateTime::currentMSecsSinceEpoch() - kDecoderMaximumInactivity; + + for (auto it=decoder_cache_->begin(); it!=decoder_cache_->end(); ) { + DecoderPair decoder = it.value(); + + if (decoder.decoder->GetLastAccessedTime() < min_age) { + decoder.decoder->Close(); + it = decoder_cache_->erase(it); + } else { + it++; + } + } +} + +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; } - RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); + 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 1059ecd9e..e11ab0b4c 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -30,12 +30,44 @@ #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 { -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: @@ -78,7 +110,6 @@ public: time = t; color_manager = colorman; use_cache = false; - priority = RenderTicketPriority::kNormal; return_type = kFrame; force_format = VideoParams::kFormatInvalid; force_color_output = nullptr; @@ -98,7 +129,6 @@ public: rational time; ColorManager *color_manager; bool use_cache; - RenderTicketPriority priority; ReturnType return_type; QString cache_dir; @@ -128,7 +158,6 @@ public: range = time; audio_params = aparam; generate_waveforms = false; - priority = RenderTicketPriority::kNormal; clamp = true; } @@ -136,7 +165,6 @@ public: TimeRange range; AudioParams audio_params; bool generate_waveforms; - RenderTicketPriority priority; bool clamp; }; @@ -149,7 +177,7 @@ public: */ RenderTicketPtr RenderAudio(const RenderAudioParams ¶ms); - virtual void RunTicket(RenderTicketPtr ticket) const override; + bool RemoveTicket(RenderTicketPtr ticket); enum TicketType { kTypeVideo, @@ -161,10 +189,8 @@ public: return backend_; } - static int GetNumberOfIdealConcurrentJobs() - { - return QThread::idealThreadCount(); - } +public slots: + void SetAggressiveGarbageCollection(bool enabled); signals: @@ -183,6 +209,19 @@ private: ShaderCache* shader_cache_; + static constexpr auto kDecoderMaximumInactivityAggressive = 1000; + static constexpr auto kDecoderMaximumInactivity = 5000; + + int aggressive_gc_; + + QTimer *decoder_clear_timer_; + + RenderThread *video_thread_; + RenderThread *audio_thread_; + +private slots: + void ClearOldDecoders(); + }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 2cdc10130..b310ea895 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -388,6 +388,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; @@ -494,7 +498,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()); @@ -549,11 +555,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 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob &val) return nullptr; } +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 c2fc5d6c7..a537ccf89 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 { @@ -58,10 +58,7 @@ protected: virtual TexturePtr ProcessVideoCacheJob(const CacheJob &val) 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 { 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/render/videoparams.cpp b/app/render/videoparams.cpp index 1cea23b29..8403c49ee 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 fcb1e804c..26fae3cfe 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/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/task/render/render.cpp b/app/task/render/render.cpp index df2148cab..840ffca2a 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(); @@ -232,6 +234,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/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 8b33d72db..000000000 --- a/app/threading/threadpool.cpp +++ /dev/null @@ -1,111 +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(); - } - - 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_)); - } - - // 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 3c1143f55..000000000 --- a/app/threading/threadpool.h +++ /dev/null @@ -1,71 +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}; - std::atomic_int available_count_; - -}; - -} // 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/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/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index cfc2ae781..2f68a272f 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) { @@ -126,7 +126,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; @@ -138,12 +141,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 @@ -164,7 +167,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 cf5d2e8f0..1d58b1d72 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..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,17 +56,18 @@ 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); + + // 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 +255,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) { @@ -260,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 94648c22e..89a952eb0 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,24 +31,18 @@ namespace olive { -class ManagedDisplayWidgetOpenGL : public QOpenGLWidget +class ManagedDisplayWidgetOpenGL : public QOpenGLWindow { Q_OBJECT public: - ManagedDisplayWidgetOpenGL(QWidget* parent = nullptr) : - QOpenGLWidget(parent) - { - } + ManagedDisplayWidgetOpenGL() = default; signals: + // Render signals void OnInit(); - void OnPaint(); - void OnDestroy(); - void OnMouseMove(QMouseEvent* e); - protected: virtual void initializeGL() override { @@ -63,13 +57,6 @@ protected: emit OnPaint(); } - virtual void mouseMoveEvent(QMouseEvent* e) override - { - emit OnMouseMove(e); - - QOpenGLWidget::mouseMoveEvent(e); - } - private slots: void DestroyListener() { @@ -135,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 @@ -159,8 +148,6 @@ signals: void frameSwapped(); - void InnerWidgetMouseMove(QMouseEvent* event); - protected: /** * @brief Provides access to the color processor (nullptr if none is set) @@ -188,11 +175,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 +225,8 @@ private: /** * @brief Main drawing surface abstraction */ - QWidget* inner_widget_; + QWindow* inner_widget_; + QWidget *wrapper_; /** * @brief Renderer abstraction 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/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 10b944a43..f46679a51 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -84,6 +84,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(); 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(); 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/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/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) 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/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 4bb2de0ee..03914435a 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -569,6 +569,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)) { @@ -639,7 +655,7 @@ void TimelineWidget::PasteInsert() void TimelineWidget::DeleteInToOut(bool ripple) { if (!GetConnectedNode() - || !GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { + || !GetConnectedNode()->GetWorkArea()->enabled()) { return; } @@ -649,8 +665,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(); @@ -658,7 +674,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)); @@ -666,17 +682,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); @@ -1079,6 +1095,11 @@ void TimelineWidget::ShowContextMenu() connect(autocache_action, &QAction::triggered, this, &TimelineWidget::SetSelectedClipsAutocaching); 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); @@ -1221,6 +1242,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 ff431e792..d0e13e0fc 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; @@ -277,6 +279,7 @@ signals: void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); + void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); void RevealViewerInProject(ViewerOutput *r); protected: @@ -427,6 +430,7 @@ private slots: void SignalBlockSelectionChange(); + void RevealInFootageViewer(); void RevealInProject(); void RenameSelectedBlocks(); 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/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 615355ecc..d8c19d227 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->AddCachePassthroughFrom(static_cast(p.block)); } @@ -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()); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index c90d8b078..634188e65 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,121 @@ 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 = (mode == kIn) ? c->previous() : c; + 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 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 4f4167bdd..12434a91d 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -585,7 +585,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(); @@ -752,12 +752,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); } } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 141a28b70..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,61 +378,15 @@ 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())); - } - - p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().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; - 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); @@ -384,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()) { @@ -422,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 9e42c688b..fab83276a 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -102,9 +102,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 8092cb224..9b5403942 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -86,7 +86,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/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/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index e2f369ccc..66188c9fc 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" @@ -72,19 +73,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 +113,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()); @@ -210,9 +209,10 @@ 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::UpdateStack); + connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); VideoParams vp = n->GetVideoParams(); @@ -233,10 +233,9 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) dw->ConnectColorManager(color_manager); } - UpdateStack(); + UpdateWaveformViewFromMode(); waveform_view_->SetViewer(GetConnectedNode()); - waveform_view_->ConnectTimelinePoints(GetConnectedNode()->GetTimelinePoints()); UpdateRendererVideoParameters(); UpdateRendererAudioParameters(); @@ -254,9 +253,10 @@ 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::UpdateStack); + disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); CloseAudioProcessor(); @@ -272,10 +272,9 @@ 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::UpdateStack, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ViewerWidget::UpdateWaveformViewFromMode, Qt::QueuedConnection); SetGizmos(nullptr); } @@ -286,6 +285,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); @@ -381,8 +390,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"), @@ -528,11 +537,35 @@ 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(); } +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_); @@ -551,7 +584,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; } @@ -674,6 +707,7 @@ void ViewerWidget::ForceRequeueFromCurrentTime() RenderTicketWatcher *watcher = RequestNextFrameForQueue(); if (!first_requeue_watcher_) { first_requeue_watcher_ = watcher; + connect(first_requeue_watcher_, &RenderTicketWatcher::destroyed, this, &ViewerWidget::HandleFirstRequeueDestroy); } } } @@ -705,7 +739,7 @@ void ViewerWidget::UpdateTextureFromNode() // Clear queue because we want this frame more than any others auto_cacher_->ClearSingleFrameRenders(); - 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(); @@ -736,6 +770,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) viewer->auto_cacher_->SetRendersPaused(true); } + RenderManager::instance()->SetAggressiveGarbageCollection(true); + // Disarm recording if armed if (record_armed_) { DisarmRecording(); @@ -833,6 +869,8 @@ void ViewerWidget::PauseInternal() } UpdateTextureFromNode(); + + RenderManager::instance()->SetAggressiveGarbageCollection(false); } prequeuing_video_ = false; @@ -854,7 +892,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))); } } } @@ -904,7 +942,7 @@ void ViewerWidget::SetDisplayImage(QVariant frame) } } -RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool increment) +RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment) { RenderTicketWatcher *watcher = nullptr; @@ -920,19 +958,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(); @@ -1000,33 +1038,22 @@ 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())); } +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)); @@ -1102,7 +1129,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(); } } } @@ -1182,6 +1211,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) { @@ -1245,11 +1286,14 @@ 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); + + 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); } { @@ -1273,9 +1317,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; } @@ -1403,11 +1447,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 { @@ -1483,7 +1527,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; @@ -1565,13 +1609,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 68c6501e1..d0e4acd42 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" @@ -51,6 +50,13 @@ class ViewerWidget : public TimeBasedWidget { Q_OBJECT public: + enum WaveformMode { + kWFAutomatic, + kWFViewerOnly, + kWFWaveformOnly, + kWFViewerAndWaveform + }; + ViewerWidget(QWidget* parent = nullptr); virtual ~ViewerWidget() override; @@ -157,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; @@ -195,9 +203,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(); @@ -221,7 +229,7 @@ private: void CloseAudioProcessor(); - QStackedWidget* stack_; + void SetWaveformMode(WaveformMode wf); ViewerSizer* sizer_; @@ -282,6 +290,8 @@ private: bool enable_audio_scrubbing_; + WaveformMode waveform_mode_; + private slots: void PlaybackTimerUpdate(); @@ -297,10 +307,12 @@ private slots: void SetZoomFromMenu(QAction* action); - void UpdateStack(); + void UpdateWaveformViewFromMode(); void ContextMenuSetFullScreen(QAction* action); + void ContextMenuSetPlaybackRes(QAction* action); + void ContextMenuDisableSafeMargins(); void ContextMenuSetSafeMargins(); @@ -315,7 +327,7 @@ private slots: void ViewerInvalidatedVideoRange(const olive::TimeRange &range); - void ManualSwitchToWaveform(bool e); + void UpdateWaveformModeFromMenu(QAction *a); void DragEntered(QDragEnterEvent* event); @@ -336,6 +348,8 @@ private slots: void CreateAddableAt(const QRectF &f); + void HandleFirstRequeueDestroy(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 18bbcd1c0..eaf5e9810 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -72,8 +72,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(); @@ -116,7 +114,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) @@ -241,185 +239,53 @@ 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)) { - - 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(); - } - - super::mousePressEvent(event); - + if (o != this->inner_widget()) { + return super::eventFilter(o, e); } -} -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()); - } - - } 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); - } - - 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; - } + switch (e->type()) { + case QEvent::MouseButtonPress: + { + 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))) { + return true; + } + break; + case QEvent::MouseButtonRelease: + if (OnMouseRelease(static_cast(e))) { + return true; + } + 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() @@ -510,7 +376,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_); @@ -524,7 +390,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)); @@ -574,7 +440,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_)); } } @@ -596,7 +462,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()); @@ -641,10 +507,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(); @@ -790,56 +660,261 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) { QTransform gizmo_transform = GenerateDisplayTransform(); - ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); + // 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); - QRectF transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect(); - text_edit->setGeometry(transformed_geom.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())); - ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); + QRect global_popup_area = global_text_area; - QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height())); + // Create toolbar + ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(popup); + text_edit->ConnectToolBar(toolbar); + + // 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))); + } + }); +} + +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(); + } - // 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); - }); + 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) diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 7359087f6..2b1491f5e 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" @@ -131,6 +130,8 @@ public: return &timer_; } + virtual bool eventFilter(QObject *o, QEvent *e) override; + public slots: /** * @brief Set the transformation matrix to draw with @@ -216,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. @@ -279,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(). */ @@ -391,8 +375,6 @@ private: bool queue_starved_; private slots: - void EmitColorAtCursor(QMouseEvent* e); - void UpdateFromQueue(); void TextEditChanged(); diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 76fd94037..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()); @@ -134,7 +122,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) @@ -176,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_) { @@ -323,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); @@ -509,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(); } } @@ -518,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); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 176575e40..e7f017a69 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 @@ -32,6 +33,7 @@ #include "dialog/about/about.h" #include "mainmenu.h" #include "mainstatusbar.h" +#include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -43,7 +45,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) @@ -486,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() { @@ -565,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); };