Merge branch 'master' into cache-update
This commit is contained in:
@@ -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++
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
@@ -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() :
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -522,6 +522,9 @@ bool Core::AddOpenProjectFromTask(Task *task)
|
||||
return true;
|
||||
} else {
|
||||
delete project;
|
||||
if (open_projects_.empty()) {
|
||||
CreateNewProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<QSslError> &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());
|
||||
|
||||
@@ -65,6 +65,8 @@ protected:
|
||||
private slots:
|
||||
void ReplyFinished(QNetworkReply *reply);
|
||||
|
||||
void HandleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||
|
||||
void AttemptToFindReport();
|
||||
|
||||
void ReadProcessHasData();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Track*>(sender()), height);
|
||||
});
|
||||
|
||||
track->set_type(type_);
|
||||
track->set_sequence(parent());
|
||||
|
||||
@@ -101,6 +101,8 @@ signals:
|
||||
|
||||
void TrackRemoved(Track* track);
|
||||
|
||||
void TrackHeightChanged(Track *track, int height);
|
||||
|
||||
private:
|
||||
void UpdateTrackIndexesFrom(int index);
|
||||
|
||||
|
||||
@@ -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>();
|
||||
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>();;
|
||||
rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational();
|
||||
if (!r.isNaN()) {
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "timeline/timelinepoints.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include "node/output/track/tracklist.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "timeline/timelinepoints.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<NodeKeyframe::Type>(attr.value().toInt()));
|
||||
key->set_type_no_bezier_adj(static_cast<NodeKeyframe::Type>(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<ViewerOutput*>(node)) {
|
||||
// Write TimelinePoints
|
||||
writer->writeStartElement(QStringLiteral("points"));
|
||||
SaveTimelinePoints(writer, viewer->GetTimelinePoints());
|
||||
SaveTimelinePoints(writer, viewer);
|
||||
writer->writeEndElement(); // points
|
||||
|
||||
if (Footage *footage = dynamic_cast<Footage*>(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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -45,6 +45,8 @@ public:
|
||||
audio_monitor_->SetParams(params);
|
||||
}
|
||||
|
||||
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
||||
private:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -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<ViewerOutput *> FootageViewerPanel::GetSelectedFootage() const
|
||||
{
|
||||
QVector<ViewerOutput *> list;
|
||||
@@ -51,7 +56,7 @@ QVector<ViewerOutput *> FootageViewerPanel::GetSelectedFootage() const
|
||||
|
||||
void FootageViewerPanel::Retranslate()
|
||||
{
|
||||
ViewerPanelBase::Retranslate();
|
||||
super::Retranslate();
|
||||
|
||||
SetTitle(tr("Footage Viewer"));
|
||||
}
|
||||
|
||||
@@ -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<FootageViewerWidget*>(GetTimeBasedWidget());
|
||||
}
|
||||
|
||||
virtual QVector<ViewerOutput *> GetSelectedFootage() const override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<RenderTicket>();
|
||||
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<rational>(),
|
||||
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<AudioWaveformCache*>(cache);
|
||||
rap.priority = priority;
|
||||
rap.clamp = false;
|
||||
|
||||
RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(rap);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERCONTEXTTHREADWRAPPER_H
|
||||
#define RENDERCONTEXTTHREADWRAPPER_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#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
|
||||
+140
-23
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+50
-11
@@ -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<RenderTicketPtr> 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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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::TicketType>() != 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<ColorManager>(ticket_->property("colormanager"));
|
||||
ColorProcessorPtr cp = ColorProcessor::Create(color_manager, input_cs, color_manager->GetReferenceColorSpace());
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -132,6 +132,40 @@ private:
|
||||
|
||||
using RenderTicketPtr = std::shared_ptr<RenderTicket>;
|
||||
|
||||
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)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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<std::mutex> lock(high_mutex_);
|
||||
high_tasks_.emplace_back(std::move(ticket));
|
||||
high_cond_.notify_one();
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(task_mutex_);
|
||||
tasks_.emplace_back(std::move(ticket));
|
||||
cond_.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
bool ThreadPool::RemoveTicket(RenderTicketPtr ticket)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<TaskType> *queue, std::mutex *mutex, std::condition_variable *cond)
|
||||
{
|
||||
while (true) {
|
||||
TaskType task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef THREADPOOL_H
|
||||
#define THREADPOOL_H
|
||||
|
||||
#include "threading/threadticket.h"
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
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<TaskType> *queue, std::mutex *mutex, std::condition_variable *cond);
|
||||
|
||||
std::vector<std::thread> worker_threads_;
|
||||
std::deque<TaskType> tasks_;
|
||||
std::mutex task_mutex_;
|
||||
std::condition_variable cond_;
|
||||
|
||||
std::thread high_thread_;
|
||||
std::deque<TaskType> 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
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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
|
||||
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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_;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TIMELINEPOINTS_H
|
||||
#define TIMELINEPOINTS_H
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#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
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "audiomonitor.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
|
||||
@@ -35,8 +36,7 @@ const int kMaximumSmoothness = 8;
|
||||
|
||||
QVector<AudioMonitor*> 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;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#define AUDIOMONITORWIDGET_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#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;
|
||||
|
||||
|
||||
@@ -23,18 +23,19 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
|
||||
#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<ManagedDisplayWidgetOpenGL*>(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<ManagedDisplayWidgetOpenGL*>(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<ManagedDisplayWidgetOpenGL*>(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<QContextMenuEvent*>(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<QMouseEvent*>(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();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLWindow>
|
||||
|
||||
#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
|
||||
|
||||
@@ -291,7 +291,7 @@ void MenuShared::NestTriggered()
|
||||
|
||||
void MenuShared::DefaultTransitionTriggered()
|
||||
{
|
||||
qDebug() << "FIXME: Stub";
|
||||
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->AddDefaultTransitionsToSelected();
|
||||
}
|
||||
|
||||
void MenuShared::TimecodeDisplayTriggered()
|
||||
|
||||
@@ -84,6 +84,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
||||
NodeParamViewItemTitleBar *title_bar = static_cast<NodeParamViewItemTitleBar*>(c->titleBarWidget());
|
||||
|
||||
if (i == Track::kVideo || i == Track::kAudio) {
|
||||
c->SetEffectType(static_cast<Track::Type>(i));
|
||||
title_bar->SetAddEffectButtonVisible(true);
|
||||
title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast<Track::Type>(i))));
|
||||
} else {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NodeParamViewItem*> items_;
|
||||
|
||||
Track::Type type_;
|
||||
|
||||
private slots:
|
||||
void AddEffectButtonClicked();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QUrl>
|
||||
@@ -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<Footage*>(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<Folder*>(context_menu_items_.first()), false);
|
||||
|
||||
@@ -185,6 +185,8 @@ private slots:
|
||||
|
||||
void RevealSelectedFootage();
|
||||
|
||||
void ReplaceSelectedFootage();
|
||||
|
||||
void OpenContextMenuItemInNewTab();
|
||||
|
||||
void OpenContextMenuItemInNewWindow();
|
||||
|
||||
@@ -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<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
if (markers_) {
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerAdded, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerRemoved, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerModified, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
}
|
||||
|
||||
points_ = points;
|
||||
markers_ = markers;
|
||||
|
||||
if (points_) {
|
||||
connect(points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
if (markers_) {
|
||||
connect(markers_, &TimelineMarkerList::MarkerAdded, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(markers_, &TimelineMarkerList::MarkerRemoved, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(markers_, &TimelineMarkerList::MarkerModified, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ResizableTimelineScrollBar::ConnectWorkArea(TimelineWorkArea *workarea)
|
||||
{
|
||||
if (workarea_) {
|
||||
disconnect(workarea_, &TimelineWorkArea::RangeChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
disconnect(workarea_, &TimelineWorkArea::EnabledChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
}
|
||||
|
||||
workarea_ = workarea;
|
||||
|
||||
if (workarea_) {
|
||||
connect(workarea_, &TimelineWorkArea::RangeChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&ResizableTimelineScrollBar::update));
|
||||
connect(workarea_, &TimelineWorkArea::EnabledChanged, this, static_cast<void (ResizableTimelineScrollBar::*)()>(&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();
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<rational> &start_times, ration
|
||||
// Snap to clip markers too
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(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<rational> &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<rational> &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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -569,6 +569,22 @@ void TimelineWidget::ToggleLinksOnSelected()
|
||||
Core::instance()->undo_stack()->push(new NodeLinkManyCommand(blocks, link));
|
||||
}
|
||||
|
||||
void TimelineWidget::AddDefaultTransitionsToSelected()
|
||||
{
|
||||
QVector<ClipBlock*> blocks;
|
||||
|
||||
foreach (Block* item, GetSelectedBlocks()) {
|
||||
// Only clips can be linked
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(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<Track*> 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<NodeGraph*>(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<quintptr>(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<quintptr>(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<QAction*>(sender());
|
||||
|
||||
ViewerOutput *item_to_reveal = reinterpret_cast<ViewerOutput*>(a->data().value<quintptr>());
|
||||
TimeRange r = a->property("range").value<TimeRange>();
|
||||
|
||||
emit RevealViewerInFootageViewer(item_to_reveal, r);
|
||||
}
|
||||
|
||||
void TimelineWidget::RevealInProject()
|
||||
{
|
||||
QAction *a = static_cast<QAction*>(sender());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -689,6 +689,8 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
InsertGapsAtGhostDestination(command);
|
||||
}
|
||||
|
||||
QMap<Node*, Node*> 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<Block*>(Node::CopyNodeInGraph(block, command));
|
||||
Block *new_block = static_cast<Block*>(Node::CopyNodeInGraph(block, command));
|
||||
relinks.insert(block, new_block);
|
||||
block = new_block;
|
||||
|
||||
if (ClipBlock *new_clip = dynamic_cast<ClipBlock*>(block)) {
|
||||
new_clip->AddCachePassthroughFrom(static_cast<ClipBlock*>(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());
|
||||
|
||||
@@ -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<Block*> blocks_to_append_gap_to;
|
||||
QVector<Track*> 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<GapBlock*>(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<ClipBlock*>(c->previous()))) {
|
||||
// Do nothing, assume this will be handled by a dual transition from that clip
|
||||
} else if (dynamic_cast<GapBlock*>(c->previous()) || !c->previous()) {
|
||||
// Create in transition
|
||||
AddTransition(c, kIn);
|
||||
}
|
||||
|
||||
// Handle out transition
|
||||
if (clips_.contains(static_cast<ClipBlock*>(c->next()))) {
|
||||
AddTransition(c, kOutDual);
|
||||
} else if (dynamic_cast<GapBlock*>(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<rational>();
|
||||
|
||||
// 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<ClipBlock*>(c->next()), half_length);
|
||||
ValidateTransitionLength(c, half_length);
|
||||
|
||||
transition_length = half_length * 2;
|
||||
|
||||
if (transition_length > 0) {
|
||||
AdjustClipLength(static_cast<ClipBlock*>(c->next()), half_length, false);
|
||||
AdjustClipLength(c, half_length, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_length > 0) {
|
||||
if (TransitionBlock *transition = dynamic_cast<TransitionBlock*>(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_;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -358,6 +358,61 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineAddDefaultTransitionCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineAddDefaultTransitionCommand(const QVector<ClipBlock*> &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<ClipBlock*> clips_;
|
||||
rational timebase_;
|
||||
QVector<UndoCommand*> commands_;
|
||||
|
||||
QHash<ClipBlock*, rational> lengths_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEUNDOGENERAL_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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerAdded, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerRemoved, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(markers_, &TimelineMarkerList::MarkerModified, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
}
|
||||
|
||||
timeline_points_ = points;
|
||||
markers_ = markers;
|
||||
|
||||
if (timeline_points_) {
|
||||
connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
if (markers_) {
|
||||
connect(markers_, &TimelineMarkerList::MarkerAdded, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(markers_, &TimelineMarkerList::MarkerRemoved, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(markers_, &TimelineMarkerList::MarkerModified, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
}
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void SeekableWidget::SetWorkArea(TimelineWorkArea *workarea)
|
||||
{
|
||||
if (workarea_) {
|
||||
selection_manager_.ClearSelection();
|
||||
|
||||
disconnect(workarea_, &TimelineWorkArea::RangeChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
disconnect(workarea_, &TimelineWorkArea::EnabledChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
}
|
||||
|
||||
workarea_ = workarea;
|
||||
|
||||
if (workarea_) {
|
||||
connect(workarea_, &TimelineWorkArea::RangeChanged, viewport(), static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
connect(workarea_, &TimelineWorkArea::EnabledChanged, viewport(), static_cast<void (QWidget::*)()>(&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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <QScrollBar>
|
||||
|
||||
#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_;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<ViewerOutput*, rational> cached_timestamps_;
|
||||
|
||||
TimelineWorkArea *override_workarea_;
|
||||
|
||||
private slots:
|
||||
void StartFootageDrag();
|
||||
|
||||
|
||||
+102
-62
@@ -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<Sequence*>(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<RenderTicket>();
|
||||
@@ -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<WaveformMode>(a->data().toInt()));
|
||||
}
|
||||
|
||||
void ViewerWidget::DragEntered(QDragEnterEvent* event)
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+286
-211
@@ -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<DraggableGizmo*>(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<DraggableGizmo*>(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<TextGizmo*>(g)) {
|
||||
if (text->GetRect().contains(ptr)) {
|
||||
OpenTextGizmo(text, event);
|
||||
break;
|
||||
}
|
||||
switch (e->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
{
|
||||
QMouseEvent *mouse = static_cast<QMouseEvent*>(e);
|
||||
if (!(mouse->flags() & Qt::MouseEventCreatedDoubleClick)) {
|
||||
if (OnMousePress(mouse)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseMove:
|
||||
EmitColorAtCursor(static_cast<QMouseEvent*>(e));
|
||||
if (OnMouseMove(static_cast<QMouseEvent*>(e))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
if (OnMouseRelease(static_cast<QMouseEvent*>(e))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::MouseButtonDblClick:
|
||||
if (OnMouseDoubleClick(static_cast<QMouseEvent*>(e))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::DragEnter:
|
||||
emit DragEntered(static_cast<QDragEnterEvent*>(e));
|
||||
break;
|
||||
case QEvent::DragLeave:
|
||||
emit DragLeft(static_cast<QDragLeaveEvent*>(e));
|
||||
break;
|
||||
case QEvent::Drop:
|
||||
emit Dropped(static_cast<QDropEvent*>(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; i<frame_rate_averages_.size(); i++) {
|
||||
@@ -582,10 +448,10 @@ void ViewerDisplayWidget::OnPaint()
|
||||
}
|
||||
average /= double(frame_rate_averages_.size());
|
||||
|
||||
DrawTextWithCrudeShadow(&p, inner_widget()->rect(), 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<Track*> &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<quintptr>(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<DraggableGizmo*>(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<DraggableGizmo*>(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<TextGizmo*>(g)) {
|
||||
if (text->GetRect().contains(ptr)) {
|
||||
OpenTextGizmo(text, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e)
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#define VIEWERGLWIDGET_H
|
||||
|
||||
#include <QMatrix4x4>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QRubberBand>
|
||||
|
||||
#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();
|
||||
|
||||
@@ -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<ViewerTextEditorToolBar*>(test)
|
||||
|| dynamic_cast<SliderLadder*>(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_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QScreen>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <QOffscreenSurface>
|
||||
@@ -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);
|
||||
|
||||
@@ -196,6 +196,7 @@ private slots:
|
||||
void ShowWelcomeDialog();
|
||||
|
||||
void RevealViewerInProject(ViewerOutput *r);
|
||||
void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user