Merge branch 'master' into cache-update

This commit is contained in:
itsmattkc
2022-07-01 22:32:49 -05:00
98 changed files with 1638 additions and 1480 deletions
+6 -6
View File
@@ -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
View File
@@ -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)
+6 -2
View File
@@ -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() :
+5 -1
View File
@@ -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
}
+4
View File
@@ -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);
+3
View File
@@ -522,6 +522,9 @@ bool Core::AddOpenProjectFromTask(Task *task)
return true;
} else {
delete project;
if (open_projects_.empty()) {
CreateNewProject();
}
}
}
+18 -1
View File
@@ -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());
+2
View File
@@ -65,6 +65,8 @@ protected:
private slots:
void ReplyFinished(QNetworkReply *reply);
void HandleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void AttemptToFindReport();
void ReadProcessHasData();
+3 -4
View File
@@ -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();
}
+4 -7
View File
@@ -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]);
}
}
+11 -6
View File
@@ -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());
}
}
+2
View File
@@ -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;
+7 -3
View File
@@ -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_;
+1
View File
@@ -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
+3
View File
@@ -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());
+2
View File
@@ -101,6 +101,8 @@ signals:
void TrackRemoved(Track* track);
void TrackHeightChanged(Track *track, int height);
private:
void UpdateTrackIndexesFrom(int index);
+4 -3
View File
@@ -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;
}
+6 -6
View File
@@ -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_;
-1
View File
@@ -29,7 +29,6 @@
#include "node/output/viewer/viewer.h"
#include "render/audioparams.h"
#include "render/videoparams.h"
#include "timeline/timelinepoints.h"
namespace olive {
-1
View File
@@ -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;
+22 -5
View File
@@ -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"));
+2
View File
@@ -45,6 +45,8 @@ public:
audio_monitor_->SetParams(params);
}
virtual bool eventFilter(QObject *o, QEvent *e) override;
private:
virtual void Retranslate() override;
+9 -4
View File
@@ -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"));
}
+8
View File
@@ -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:
+1
View File
@@ -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()
+6
View File
@@ -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);
};
+2 -2
View File
@@ -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
+10 -13
View File
@@ -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);
+4 -6
View File
@@ -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
-181
View File
@@ -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));
}
}
-86
View File
@@ -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
View File
@@ -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 &params)
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 &params)
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
View File
@@ -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 &params);
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();
};
}
+28 -1
View File
@@ -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());
+2 -5
View File
@@ -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 &params, 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)
+9
View File
@@ -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) {
+2
View File
@@ -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);
+2 -2
View File
@@ -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());
+4
View File
@@ -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;
}
+1 -2
View File
@@ -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 {
-26
View File
@@ -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
)
-111
View File
@@ -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();
}
}
-71
View File
@@ -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
-103
View File
@@ -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);
}
}
-64
View File
@@ -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
-2
View File
@@ -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
-54
View File
@@ -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_;
}
}
-53
View File
@@ -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
+9 -6
View File
@@ -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;
+3 -3
View File
@@ -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;
+61 -7
View File
@@ -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();
+24 -21
View File
@@ -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
+1 -1
View File
@@ -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_;
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
}
}
+46 -31
View File
@@ -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);
+11
View File
@@ -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();
+38 -7
View File
@@ -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();
+1 -1
View File
@@ -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();
+18 -1
View File
@@ -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);
}
}
+129 -90
View File
@@ -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;
}
}
}
}
+12 -5
View File
@@ -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_;
+2 -3
View File
@@ -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;
+2 -1
View File
@@ -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
+16
View File
@@ -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)
+5
View File
@@ -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
View File
@@ -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)
+20 -6
View File
@@ -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
View File
@@ -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)
+9 -27
View File
@@ -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();
+8 -45
View File
@@ -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_)));
}
}
+2 -4
View File
@@ -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);
+20 -1
View File
@@ -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);
+1
View File
@@ -196,6 +196,7 @@ private slots:
void ShowWelcomeDialog();
void RevealViewerInProject(ViewerOutput *r);
void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
};