diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 03b8f6e23..4e693987d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -107,6 +107,7 @@ else() -Wall -Wextra -Wno-unused-parameter + -Wshadow ) endif() diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 3beeb2cbb..08c6c27b0 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -231,10 +231,13 @@ void AudioVisualWaveform::DrawSample(QPainter *painter, const QVector(1.0f)); + qfloat16 min = qMax(sample.at(i).min, static_cast(-1.0)); + if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) { int channel_bottom = y + channel_height * (i + 1); - int diff = qRound((sample.at(i).max - sample.at(i).min) * channel_half_height); + int diff = qRound((max - min) * channel_half_height); painter->drawLine(x, channel_bottom - diff, @@ -244,9 +247,9 @@ void AudioVisualWaveform::DrawSample(QPainter *painter, const QVectordrawLine(x, - channel_mid + qRound(sample.at(i).min * static_cast(channel_half_height)), + channel_mid + qRound(min * static_cast(channel_half_height)), x, - channel_mid + qRound(sample.at(i).max * static_cast(channel_half_height))); + channel_mid + qRound(max * static_cast(channel_half_height))); } } } diff --git a/app/core.cpp b/app/core.cpp index d737a0563..e8f34fcf4 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -151,12 +151,39 @@ int Core::execute(QCoreApplication* a) return exit_code; } +void Core::DeclareTypesForQt() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + void Core::Start() { // Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes // the fact that some of the config paths set by default rely on the app name having been set (in main()) Config::Current().SetDefaults(); + // Load application config + Config::Load(); + // Declare custom types for Qt signal/slot system DeclareTypesForQt(); @@ -169,9 +196,8 @@ void Core::Start() // Initialize task manager TaskManager::CreateInstance(); - // Load application config - Config::Load(); - + // Initialize OpenGL service + OpenGLProxy::CreateInstance(); // // Start application @@ -199,6 +225,8 @@ void Core::Stop() } } + OpenGLProxy::DestroyInstance(); + MenuShared::DestroyInstance(); TaskManager::DestroyInstance(); @@ -480,9 +508,11 @@ void Core::AddOpenProject(ProjectPtr p) void Core::AddOpenProjectFromTask(Task *task) { QList projects = static_cast(task)->GetLoadedProjects(); + QList layouts = static_cast(task)->GetLoadedLayouts(); - foreach (ProjectPtr p, projects) { - AddOpenProject(p); + for (int i=0; iLoadLayout(layouts.at(i)); } } @@ -629,29 +659,6 @@ void Core::OpenStartupProject() } } -void Core::DeclareTypesForQt() -{ - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); -} - void Core::StartGUI(bool full_screen) { // Set UI style @@ -1011,21 +1018,11 @@ void Core::OpenProjectInternal(const QString &filename) ProjectLoadTask* plm = new ProjectLoadTask(filename); - if (gui_active_) { + TaskDialog* task_dialog = new TaskDialog(plm, tr("Load Project"), main_window()); - TaskDialog* task_dialog = new TaskDialog(plm, tr("Load Project"), main_window()); + connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask); - connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask); - - task_dialog->open(); - - } else { - - //connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject); - - - - } + task_dialog->open(); } int Core::CountFilesInFileList(const QFileInfoList &filenames) diff --git a/app/dialog/richtext/richtext.cpp b/app/dialog/richtext/richtext.cpp index 5b4303530..8807156b9 100644 --- a/app/dialog/richtext/richtext.cpp +++ b/app/dialog/richtext/richtext.cpp @@ -50,7 +50,6 @@ RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) : toolbar_layout->addWidget(font_combo_); size_slider_ = new FloatSlider(); size_slider_->SetMinimum(0.1); - size_slider_->SetLadderEnabled(true); size_slider_->SetLadderElementCount(1); size_slider_->setToolTip(tr("Font Size")); toolbar_layout->addWidget(size_slider_); diff --git a/app/node/input.cpp b/app/node/input.cpp index 3ec99b36f..d17656194 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -81,13 +81,15 @@ QString NodeInput::name() void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) { - XMLAttributeLoop(reader, attr) { - if (cancelled && *cancelled) { - return; - } + { + XMLAttributeLoop(reader, attr) { + if (cancelled && *cancelled) { + return; + } - if (attr.name() == QStringLiteral("keyframing")) { - set_is_keyframing(attr.value() == QStringLiteral("1")); + if (attr.name() == QStringLiteral("keyframing")) { + set_is_keyframing(attr.value() == QStringLiteral("1")); + } } } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 50ae6c2d8..b5ee9d76d 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -285,8 +285,13 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o } } else if (pairing == kPairTextureMatrix) { // Only allow matrix multiplication - if (operation != kOpMultiply - || number_val.data().value().isIdentity()) { + bool matrix_is_identity = false; + + // FIXME: The matrix in the shader is transformed around footage+sequence resolution so we + // need to do that here to determine if the matrix is truly identity. But to do that, + // we need access to the texture parameters which is currently not possible. + + if (operation != kOpMultiply || matrix_is_identity) { operation_is_noop = true; } else { // It's likely an alpha channel will result from this operation diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 67a19c91b..abe36e1e7 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -151,9 +151,9 @@ void TrackList::RemoveTrack() void TrackList::TrackConnected(NodeEdgePtr edge) { - int track_index = track_input_->IndexOfSubParameter(edge->input()); + int input_index = track_input_->IndexOfSubParameter(edge->input()); - Q_ASSERT(track_index >= 0); + Q_ASSERT(input_index >= 0); Node* connected_node = edge->output()->parentNode(); @@ -163,7 +163,7 @@ void TrackList::TrackConnected(NodeEdgePtr edge) { // Find "real" index TrackOutput* next = nullptr; - for (int i=track_index+1; iGetSize(); i++) { + for (int i=input_index+1; iGetSize(); i++) { Node* that_track = track_input_->At(i)->get_connected_node(); if (that_track && that_track->IsTrack()) { diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 8078ade32..45fd6b23d 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -129,12 +129,20 @@ void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, Node void ViewerOutput::set_video_params(const VideoParams &video) { + bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height(); + bool timebase_changed = video_params_.time_base() != video.time_base(); + video_params_ = video; - video_frame_cache_.SetTimebase(video_params_.time_base()); + if (size_changed) { + emit SizeChanged(video_params_.width(), video_params_.height()); + } + + if (timebase_changed) { + video_frame_cache_.SetTimebase(video_params_.time_base()); + emit TimebaseChanged(video_params_.time_base()); + } - emit SizeChanged(video_params_.width(), video_params_.height()); - emit TimebaseChanged(video_params_.time_base()); emit ParamsChanged(); } diff --git a/app/node/value.cpp b/app/node/value.cpp index e2f9913c5..ef5ac05bd 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -44,7 +44,12 @@ void NodeValueDatabase::Insert(const NodeInput *key, const NodeValueTable &value NodeValueTable NodeValueDatabase::Merge() const { - return NodeValueTable::Merge(tables_.values()); + QHash copy = tables_; + + // Kinda hacky, but we don't need this table to slipstream + copy.remove(QStringLiteral("global")); + + return NodeValueTable::Merge(copy.values()); } NodeValue::NodeValue() : @@ -176,7 +181,6 @@ NodeValueTable NodeValueTable::Merge(QList tables) NodeValueTable merged_table; // Slipstreams all tables together - // FIXME: I don't actually know if this is the right approach... foreach (const NodeValueTable& t, tables) { if (row >= t.Count()) { continue; diff --git a/app/node/value.h b/app/node/value.h index 7efb8592a..448c1c54d 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -108,14 +108,21 @@ public: using const_iterator = QHash::const_iterator; - inline QHash::const_iterator begin() const { + inline QHash::const_iterator begin() const + { return tables_.cbegin(); } - inline QHash::const_iterator end() const { + inline QHash::const_iterator end() const + { return tables_.cend(); } + inline bool contains(const QString& s) const + { + return tables_.contains(s); + } + private: QHash tables_; diff --git a/app/panel/curve/curve.cpp b/app/panel/curve/curve.cpp index 2c9fc0e4b..b6179d258 100644 --- a/app/panel/curve/curve.cpp +++ b/app/panel/curve/curve.cpp @@ -49,11 +49,6 @@ void CurvePanel::SetInput(NodeInput *input) Retranslate(); } -void CurvePanel::SetTimeTarget(Node *target) -{ - static_cast(GetTimeBasedWidget())->SetTimeTarget(target); -} - void CurvePanel::IncreaseTrackHeight() { CurveWidget* c = static_cast(GetTimeBasedWidget()); diff --git a/app/panel/curve/curve.h b/app/panel/curve/curve.h index 4b25b2b22..982a4d0b4 100644 --- a/app/panel/curve/curve.h +++ b/app/panel/curve/curve.h @@ -39,8 +39,6 @@ public: public slots: void SetInput(NodeInput* input); - void SetTimeTarget(Node* target); - virtual void IncreaseTrackHeight() override; virtual void DecreaseTrackHeight() override; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 649998cdf..e6731e9b0 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -29,10 +29,7 @@ ParamPanel::ParamPanel(QWidget* parent) : { NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::InputDoubleClicked, this, &ParamPanel::CreateCurvePanel); - connect(view, &NodeParamView::TimeTargetChanged, this, &ParamPanel::TimeTargetChanged); connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); - connect(view, &NodeParamView::OpenedNode, this, &ParamPanel::OpeningNode); - connect(view, &NodeParamView::ClosedNode, this, &ParamPanel::ClosingNode); connect(view, &NodeParamView::FoundGizmos, this, &ParamPanel::FoundGizmos); SetTimeBasedWidget(view); @@ -51,13 +48,7 @@ void ParamPanel::SetTimestamp(const int64_t ×tamp) TimeBasedPanel::SetTimestamp(timestamp); // Ensure all CurvePanels are updated with this time too - QHash::const_iterator i; - - for (i=open_curve_panels_.begin(); i!=open_curve_panels_.end(); i++) { - if (i.value() && i.value() != sender()) { - i.value()->SetTimestamp(timestamp); - } - } + ParamViewTimeChanged(timestamp); } void ParamPanel::DeleteSelected() @@ -97,52 +88,52 @@ void ParamPanel::CreateCurvePanel(NodeInput *input) panel = Core::instance()->main_window()->AppendCurvePanel(); - panel->SetInput(input); - panel->SetTimebase(view->timebase()); + panel->ConnectViewerNode(view->GetConnectedNode()); panel->SetTimestamp(view->GetTimestamp()); - panel->SetTimeTarget(view->GetTimeTarget()); + panel->SetInput(input); - connect(view, &NodeParamView::TimebaseChanged, panel, &CurvePanel::SetTimebase); - connect(view, &NodeParamView::TimeChanged, panel, &CurvePanel::SetTimestamp); - connect(view, &NodeParamView::TimeTargetChanged, panel, &CurvePanel::SetTimeTarget); - connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::SetTimestamp); - connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::TimeChanged); + connect(view, &NodeParamView::TimeChanged, this, &ParamPanel::ParamViewTimeChanged); + connect(panel, &CurvePanel::TimeChanged, this, &ParamPanel::CurvePanelTimeChanged); connect(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel); open_curve_panels_.insert(input, panel); } -void ParamPanel::OpeningNode(Node *n) -{ - QList inputs = n->GetInputsIncludingArrays(); - - foreach (NodeInput* i, inputs) { - if (open_curve_panels_.contains(i)) { - // We had a CurvePanel open for this input that was closed in ClosingNode(), re-open it - CreateCurvePanel(i); - } - } -} - -void ParamPanel::ClosingNode(Node *n) -{ - QList inputs = n->GetInputsIncludingArrays(); - - foreach (NodeInput* i, inputs) { - CurvePanel* panel = open_curve_panels_.value(i); - - // Close the panel (this also destroys it), but keep a reference in the hash - if (panel) { - panel->close(); - open_curve_panels_.insert(i, nullptr); - } - } -} - void ParamPanel::ClosingCurvePanel() { CurvePanel* panel = static_cast(sender()); open_curve_panels_.remove(panel->GetInput()); } +void ParamPanel::ParamViewTimeChanged(const int64_t &time) +{ + // Ensure all CurvePanels are updated with this time too + QHash::const_iterator i; + + for (i=open_curve_panels_.begin(); i!=open_curve_panels_.end(); i++) { + // If connected viewers are the same, set the timestamp + if (i.value()->GetConnectedViewer() == GetConnectedViewer()) { + i.value()->SetTimestamp(time); + } + } +} + +void ParamPanel::CurvePanelTimeChanged(const int64_t &time) +{ + GetTimeBasedWidget()->SetTimestamp(time); + emit GetTimeBasedWidget()->TimeChanged(time); + + CurvePanel* src = static_cast(sender()); + + // Ensure all CurvePanels are updated with this time too + QHash::const_iterator i; + + for (i=open_curve_panels_.begin(); i!=open_curve_panels_.end(); i++) { + // If connected viewers are the same and the panel isn't the source, set the timestamp + if (i.value() != src && i.value()->GetConnectedViewer() == src->GetConnectedViewer()) { + i.value()->SetTimestamp(time); + } + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 0227e45ba..12bc1715e 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -41,8 +41,6 @@ public slots: virtual void DeleteSelected() override; signals: - void TimeTargetChanged(Node* node); - void RequestSelectNode(const QList& target); void FoundGizmos(Node* node); @@ -53,15 +51,16 @@ protected: private slots: void CreateCurvePanel(NodeInput* input); - void OpeningNode(Node* n); - - void ClosingNode(Node* n); - void ClosingCurvePanel(); private: QHash open_curve_panels_; +private slots: + void ParamViewTimeChanged(const int64_t& time); + + void CurvePanelTimeChanged(const int64_t& time); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/panel/table/table.h b/app/panel/table/table.h index 0587cf51b..d08fe0de3 100644 --- a/app/panel/table/table.h +++ b/app/panel/table/table.h @@ -28,6 +28,7 @@ OLIVE_NAMESPACE_ENTER class NodeTablePanel : public TimeBasedPanel { + Q_OBJECT public: NodeTablePanel(QWidget* parent); diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index 37a0959fa..5e95e944d 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -125,6 +125,10 @@ TimeRuler *TimeBasedPanel::ruler() const void TimeBasedPanel::ConnectViewerNode(ViewerOutput *node) { + if (widget_->GetConnectedNode() == node) { + return; + } + if (widget_->GetConnectedNode()) { disconnect(widget_->GetConnectedNode(), &ViewerOutput::MediaNameChanged, this, &TimeBasedPanel::SetSubtitle); } diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index b2c5a0c22..9e89a0aaa 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -149,7 +149,7 @@ void Sequence::Save(QXmlStreamWriter *writer) const writer->writeAttribute(QStringLiteral("name"), name()); - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(viewer_output_))); + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); writer->writeStartElement(QStringLiteral("video")); diff --git a/app/project/project.cpp b/app/project/project.cpp index 014489dd6..653202c02 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -37,7 +37,7 @@ Project::Project() : root_.set_project(this); } -void Project::Load(QXmlStreamReader *reader, const QAtomicInt* cancelled) +void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, const QAtomicInt* cancelled) { XMLNodeData xml_node_data; @@ -62,7 +62,12 @@ void Project::Load(QXmlStreamReader *reader, const QAtomicInt* cancelled) } else if (reader->name() == QStringLiteral("layout")) { - Core::instance()->main_window()->LoadLayout(reader, xml_node_data); + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + *layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data); } else { reader->skipCurrentElement(); @@ -93,7 +98,8 @@ void Project::Save(QXmlStreamWriter *writer) const writer->writeEndElement(); // colormanagement // Save main window project layout - Core::instance()->main_window()->SaveLayout(writer); + MainWindowLayoutInfo main_window_info = Core::instance()->main_window()->SaveLayout(); + main_window_info.toXml(writer); writer->writeEndElement(); // project } diff --git a/app/project/project.h b/app/project/project.h index f61fc50da..ff3f8ce35 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -26,6 +26,7 @@ #include "render/colormanager.h" #include "project/item/folder/folder.h" +#include "window/mainwindow/mainwindowlayoutinfo.h" OLIVE_NAMESPACE_ENTER @@ -46,7 +47,7 @@ class Project : public QObject public: Project(); - void Load(QXmlStreamReader* reader, const QAtomicInt* cancelled); + void Load(QXmlStreamReader* reader, MainWindowLayoutInfo *layout, const QAtomicInt* cancelled); void Save(QXmlStreamWriter* writer) const; diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index a66fa8928..60e81f914 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -27,38 +27,17 @@ OLIVE_NAMESPACE_ENTER OpenGLBackend::OpenGLBackend(QObject* parent) : RenderBackend(parent) { - proxy_ = new OpenGLProxy(); - QThread* proxy_thread = new QThread(); - proxy_thread->start(QThread::IdlePriority); - proxy_->moveToThread(proxy_thread); - - if (!proxy_->Init()) { - ClearProxy(); - } } OpenGLBackend::~OpenGLBackend() { Close(); - - ClearProxy(); } RenderWorker *OpenGLBackend::CreateNewWorker() { - return new OpenGLWorker(this, proxy_); -} - -void OpenGLBackend::ClearProxy() -{ - if (proxy_) { - proxy_->thread()->quit(); - proxy_->thread()->wait(); - proxy_->thread()->deleteLater(); - proxy_->deleteLater(); - proxy_ = nullptr; - } + return new OpenGLWorker(this); } OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index a1df6dcab..7d88611f3 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -36,11 +36,6 @@ public: protected: virtual RenderWorker* CreateNewWorker() override; -private: - void ClearProxy(); - - OpenGLProxy* proxy_; - }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index aff4dac42..08aa8864c 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -33,6 +33,8 @@ OLIVE_NAMESPACE_ENTER +OpenGLProxy* OpenGLProxy::instance_ = nullptr; + OpenGLProxy::OpenGLProxy(QObject *parent) : QObject(parent), ctx_(nullptr), @@ -48,6 +50,30 @@ OpenGLProxy::~OpenGLProxy() surface_.destroy(); } +void OpenGLProxy::CreateInstance() +{ + instance_ = new OpenGLProxy(); + + QThread* proxy_thread = new QThread(); + proxy_thread->start(QThread::IdlePriority); + instance_->moveToThread(proxy_thread); + + if (!instance_->Init()) { + DestroyInstance(); + } +} + +void OpenGLProxy::DestroyInstance() +{ + if (instance_) { + instance_->thread()->quit(); + instance_->thread()->wait(); + instance_->thread()->deleteLater(); + instance_->deleteLater(); + instance_ = nullptr; + } +} + bool OpenGLProxy::Init() { // Create context object @@ -250,21 +276,21 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, shader->bind(); - NodeValueMap::const_iterator i; - for (i=job.GetValues().constBegin(); i!=job.GetValues().constEnd(); i++) { + NodeValueMap::const_iterator it; + for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) { // See if the shader has takes this parameter as an input - int variable_location = shader->uniformLocation(i.key()->id()); + int variable_location = shader->uniformLocation(it.key()->id()); if (variable_location == -1) { continue; } // This variable is used in the shader, let's set it - const QVariant& value = i.value().data(); + const QVariant& value = it.value().data(); - const NodeParam::DataType& data_type = (i.value().type() != NodeParam::kNone) - ? i.value().type() - : i.key()->data_type(); + const NodeParam::DataType& data_type = (it.value().type() != NodeParam::kNone) + ? it.value().type() + : it.key()->data_type(); switch (data_type) { case NodeInput::kInt: @@ -274,7 +300,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, shader->setUniformValue(variable_location, value.toFloat()); break; case NodeInput::kVec2: - if (i.key()->IsArray()) { + if (it.key()->IsArray()) { QVector nv = value.value< QVector >(); QVector a(nv.size()); @@ -284,7 +310,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, shader->setUniformValueArray(variable_location, a.constData(), a.size()); - int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(i.key()->id())); + int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key()->id())); if (count_location > -1) { shader->setUniformValue(count_location, a.size()); } @@ -328,7 +354,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, shader->setUniformValue(variable_location, textures_to_bind.size()); // If this texture binding is the iterative input, set it here - if (i.key() == job.GetIterativeInput()) { + if (it.key() == job.GetIterativeInput()) { iterative_input = textures_to_bind.size(); } @@ -336,7 +362,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, textures_to_bind.append(tex_id); // Set enable flag if shader wants it - int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(i.key()->id())); + int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(it.key()->id())); if (enable_param_location > -1) { shader->setUniformValue(enable_param_location, tex_id > 0); @@ -344,7 +370,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, if (tex_id > 0) { // Set texture resolution if shader wants it - int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(i.key()->id())); + int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(it.key()->id())); if (res_param_location > -1) { shader->setUniformValue(res_param_location, static_cast(texture->texture()->width() * texture->texture()->divider()), diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h index 7c6878626..ff59f76b9 100644 --- a/app/render/backend/opengl/openglproxy.h +++ b/app/render/backend/opengl/openglproxy.h @@ -41,6 +41,15 @@ public: virtual ~OpenGLProxy() override; + static void CreateInstance(); + + static void DestroyInstance(); + + static OpenGLProxy* instance() + { + return instance_; + } + /** * @brief Initialize OpenGL instance in whatever thread this object is a part of * @@ -101,6 +110,8 @@ private: OpenGLTextureCache texture_cache_; + static OpenGLProxy* instance_; + private slots: void FinishInit(); diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index b3e10b71c..ec518ffc7 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -22,15 +22,14 @@ OLIVE_NAMESPACE_ENTER -OpenGLWorker::OpenGLWorker(RenderBackend *parent, OpenGLProxy* proxy) : - RenderWorker(parent), - proxy_(proxy) +OpenGLWorker::OpenGLWorker(RenderBackend *parent) : + RenderWorker(parent) { } void OpenGLWorker::TextureToFrame(const QVariant &texture, FramePtr frame, const QMatrix4x4& mat) const { - QMetaObject::invokeMethod(proxy_, + QMetaObject::invokeMethod(OpenGLProxy::instance(), "TextureToBuffer", Qt::BlockingQueuedConnection, Q_ARG(const QVariant&, texture), @@ -42,7 +41,7 @@ QVariant OpenGLWorker::FootageFrameToTexture(StreamPtr stream, FramePtr frame) c { QVariant value; - QMetaObject::invokeMethod(proxy_, + QMetaObject::invokeMethod(OpenGLProxy::instance(), "FrameToValue", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), @@ -58,7 +57,7 @@ QVariant OpenGLWorker::CachedFrameToTexture(FramePtr frame) const { QVariant value; - QMetaObject::invokeMethod(proxy_, + QMetaObject::invokeMethod(OpenGLProxy::instance(), "PreCachedFrameToValue", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), @@ -71,7 +70,7 @@ QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, c { QVariant value; - QMetaObject::invokeMethod(proxy_, + QMetaObject::invokeMethod(OpenGLProxy::instance(), "RunNodeAccelerated", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 23b5e787f..75eed65f8 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -29,7 +29,7 @@ OLIVE_NAMESPACE_ENTER class OpenGLWorker : public RenderWorker { public: - OpenGLWorker(RenderBackend* parent, OpenGLProxy* proxy); + OpenGLWorker(RenderBackend* parent); protected: virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const override; @@ -42,9 +42,6 @@ protected: virtual bool TextureHasAlpha(const QVariant& v) const override; -private: - OpenGLProxy* proxy_; - }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index df96c9efc..b9f90e5e4 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -115,13 +115,13 @@ void RenderBackend::ClearVideoQueue() QFuture > RenderBackend::Hash(const QVector ×) { - return QtConcurrent::run(&pool_, [this](const QVector ×){ - QVector hashes(times.size()); + return QtConcurrent::run(&pool_, [this](const QVector &t){ + QVector hashes(t.size()); for (int i=0;itexture_input()->get_connected_node(), video_params_, - times.at(i)); + t.at(i)); } return hashes; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index b94ed08e0..9bce6a504 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -71,6 +71,7 @@ void PlaybackCache::SetLength(const rational &r) } else if (r > length_) { // If new length is greater, simply extend the invalidated range for now invalidated_.InsertTimeRange(range_diff); + jobs_.append({range_diff, QDateTime::currentMSecsSinceEpoch()}); } else { // If new length is smaller, removed hashes invalidated_.RemoveTimeRange(range_diff); diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 25b65aa27..5cc667a9e 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -96,21 +96,13 @@ bool ExportTask::Run() if (params_.audio_enabled()) { audio_range.append(range); + audio_data_.SetLength(range.length()); } Render(video_range, audio_range, mat, false); bool success = true; - foreach (QFuture f, write_frame_futures_) { - f.waitForFinished(); - - if (!f.result()) { - SetError(tr("Failed to write AVFrame")); - success = false; - } - } - if (params_.audio_enabled()) { // Write audio data now encoder_->WriteAudio(audio_params(), audio_data_.GetCacheFilename()); @@ -165,7 +157,7 @@ void ExportTask::FrameDownloaded(const QByteArray &hash, const std::listWriteFrame(time_map_.value(real_time), real_time); + encoder_->WriteFrame(time_map_.take(real_time), real_time); frame_time_++; @@ -180,7 +172,7 @@ void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples adjusted_range -= params_.custom_range().in(); } - audio_data_.WritePCM(adjusted_range, samples, job_time()); + audio_data_.WritePCM(adjusted_range, samples, QDateTime::currentMSecsSinceEpoch()); } OLIVE_NAMESPACE_EXIT diff --git a/app/task/export/export.h b/app/task/export/export.h index dcbae30f0..5c92adea3 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -49,8 +49,6 @@ private: QHash time_map_; - QList< QFuture > write_frame_futures_; - ColorManager* color_manager_; ExportParams params_; diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index 932a94c74..27dff9e11 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -51,13 +51,16 @@ bool ProjectLoadTask::Run() project->set_filename(filename_); - project->Load(&reader, &IsCancelled()); + MainWindowLayoutInfo layout; + + project->Load(&reader, &layout, &IsCancelled()); // Ensure project is in main thread moveToThread(qApp->thread()); if (!IsCancelled()) { projects_.append(project); + layout_info_.append(layout); } } else { reader.skipCurrentElement(); diff --git a/app/task/project/load/load.h b/app/task/project/load/load.h index fa203abfe..398853028 100644 --- a/app/task/project/load/load.h +++ b/app/task/project/load/load.h @@ -23,6 +23,7 @@ #include "project/project.h" #include "task/task.h" +#include "window/mainwindow/mainwindowlayoutinfo.h" OLIVE_NAMESPACE_ENTER @@ -32,17 +33,24 @@ class ProjectLoadTask : public Task public: ProjectLoadTask(const QString& filename); - const QList& GetLoadedProjects() + const QList& GetLoadedProjects() const { return projects_; } + const QList& GetLoadedLayouts() const + { + return layout_info_; + } + protected: virtual bool Run() override; private: QList projects_; + QList layout_info_; + QString filename_; }; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 133a6bf1d..f0da1ff54 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -93,12 +93,10 @@ void RenderTask::Render(const TimeRangeList& video_range, if (!video_range.isEmpty()) { QList existing_hashes; - foreach (const TimeRange& r, video_range) { - total_length += r.length().toDouble(); - } - times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(video_range); + total_length += video_frame_sz * times.size(); + QFuture > hash_future = backend_.Hash(times); hashes = hash_future.result(); @@ -125,7 +123,7 @@ void RenderTask::Render(const TimeRangeList& video_range, || !download_futures.empty() || !audio_lookup_table.empty())) { - if (!frame_queue.empty()) { + if (!IsCancelled() && !frame_queue.empty()) { // Pop another frame off the frame queue const HashTimePair& p = frame_queue.front(); @@ -138,11 +136,14 @@ void RenderTask::Render(const TimeRangeList& video_range, bool hash_exists = false; if (use_disk_cache) { - bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end()); + // Check if this hash is in our "existing hashes" list + hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end()); + // If not, check if it's in the filesystem if (!hash_exists) { hash_exists = QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(p.hash)); + // If so, add it to the list so we don't have to check the filesystem again later if (hash_exists) { existing_hashes.push_back(p.hash); } @@ -167,7 +168,7 @@ void RenderTask::Render(const TimeRangeList& video_range, frame_queue.pop_front(); } - if (!audio_queue.empty()) { + if (!IsCancelled() && !audio_queue.empty()) { audio_lookup_table.push_back({audio_queue.front(), backend_.RenderAudio(audio_queue.front())}); audio_queue.pop_front(); } @@ -194,9 +195,9 @@ void RenderTask::Render(const TimeRangeList& video_range, // Place it in the cache std::list times_with_hash; - for (int k=0;khash) { - times_with_hash.push_back(times.at(k)); + for (int hash_index=0;hash_indexhash) { + times_with_hash.push_back(times.at(hash_index)); } } diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 46a05a26e..86b53a8df 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -90,11 +90,11 @@ void AudioMonitor::Stop() } } -void AudioMonitor::OutputPushed(const QByteArray &data) +void AudioMonitor::OutputPushed(const QByteArray &d) { QVector v(params_.channel_count(), 0); - BytesToSampleSummary(data, v); + BytesToSampleSummary(d, v); PushValue(v); diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index d2f55f287..2b290ff14 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -45,7 +45,7 @@ public slots: void Stop(); - void OutputPushed(const QByteArray& data); + void OutputPushed(const QByteArray& d); protected: //virtual void paintEvent(QPaintEvent* event) override; diff --git a/app/widget/colorwheel/colorvalueswidget.cpp b/app/widget/colorwheel/colorvalueswidget.cpp index 1b7fd76fc..43b328949 100644 --- a/app/widget/colorwheel/colorvalueswidget.cpp +++ b/app/widget/colorwheel/colorvalueswidget.cpp @@ -203,7 +203,6 @@ FloatSlider *ColorValuesTab::CreateColorSlider() FloatSlider* fs = new FloatSlider(); fs->SetDragMultiplier(0.01); fs->SetDecimalPlaces(5); - fs->SetLadderEnabled(true); fs->SetLadderElementCount(1); connect(fs, &FloatSlider::ValueChanged, this, &ColorValuesTab::SliderChanged); return fs; diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index a78f97e13..c57072e88 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -20,6 +20,7 @@ #include "curveview.h" +#include #include #include @@ -61,6 +62,16 @@ void CurveView::Clear() void CurveView::SetTrackCount(int count) { track_count_ = count; + + track_visible_.resize(track_count_); + track_visible_.fill(true); +} + +void CurveView::SetTrackVisible(int track, bool visible) +{ + track_visible_[track] = visible; + + SetKeyframeTrackVisible(track, visible); } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) @@ -116,6 +127,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) // Draw keyframe lines for (int j=0;jsetPen(QPen(GetKeyframeColor(j), qMax(1, fontMetrics().height() / 4))); QList keys = GetKeyframesSortedByTime(j); @@ -237,21 +252,22 @@ void CurveView::VerticalScaleChangedEvent(double scale) void CurveView::wheelEvent(QWheelEvent *event) { - if (WheelEventIsAZoomEvent(event)) { - if (!event->angleDelta().isNull()) { - if (event->angleDelta().x() + event->angleDelta().y() > 0) { - emit ScaleChanged(GetScale() * 1.1); - SetYScale(GetYScale() * 1.1); - } else { - emit ScaleChanged(GetScale() * 0.9); - SetYScale(GetYScale() * 0.9); - } - } - } else { + if (!HandleZoomFromScroll(event)) { KeyframeViewBase::wheelEvent(event); } } +void CurveView::ContextMenuEvent(Menu &m) +{ + m.addSeparator(); + + // View settings + QAction* zoom_fit_action = m.addAction(tr("Zoom to Fit")); + connect(zoom_fit_action, &QAction::triggered, this, &CurveView::ZoomToFit); + + //QAction* reset_zoom_action = m.addAction(tr("Reset Zoom")); +} + QList CurveView::GetKeyframesSortedByTime(int track) { QList sorted; @@ -285,7 +301,12 @@ QList CurveView::GetKeyframesSortedByTime(int track) qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { - return -key->value().toDouble() * GetYScale(); + return GetItemYFromKeyframeValue(key->value().toDouble()); +} + +qreal CurveView::GetItemYFromKeyframeValue(double value) +{ + return -value * GetYScale(); } void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item) @@ -367,6 +388,45 @@ void CurveView::BezierControlPointDestroyed() bezier_control_points_.removeOne(item); } +void CurveView::ZoomToFit() +{ + if (item_map().isEmpty()) { + // Prevent scaling to DBL_MIN/DBL_MAX + return; + } + + QMap::const_iterator i; + + rational min_time = RATIONAL_MAX; + rational max_time = RATIONAL_MIN; + + double min_val = DBL_MAX; + double max_val = DBL_MIN; + + for (i=item_map().constBegin(); i!=item_map().constEnd(); i++) { + rational transformed_time = GetAdjustedTime(i.key()->parent()->parentNode(), + GetTimeTarget(), + i.key()->time(), + NodeParam::kOutput); + + min_time = qMin(transformed_time, min_time); + max_time = qMax(transformed_time, max_time); + + min_val = qMin(i.key()->value().toDouble(), min_val); + max_val = qMax(i.key()->value().toDouble(), max_val); + } + + double time_range = max_time.toDouble() - min_time.toDouble(); + double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range); + double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val); + + emit ScaleChanged(new_x_scale); + SetYScale(new_y_scale); + + horizontalScrollBar()->setValue(TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width())); + verticalScrollBar()->setValue(GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height())); +} + void CurveView::AddKeyframe(NodeKeyframePtr key) { KeyframeViewItem* item = AddKeyframeInternal(key); diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 2c767acd6..e051f4f04 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -30,6 +30,7 @@ OLIVE_NAMESPACE_ENTER class CurveView : public KeyframeViewBase { + Q_OBJECT public: CurveView(QWidget* parent = nullptr); @@ -39,9 +40,13 @@ public: void SetTrackCount(int count); + void SetTrackVisible(int track, bool visible); + public slots: void AddKeyframe(NodeKeyframePtr key); + void ZoomToFit(); + protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; @@ -53,10 +58,13 @@ protected: virtual void wheelEvent(QWheelEvent* event) override; + virtual void ContextMenuEvent(Menu &m) override; + private: QList GetKeyframesSortedByTime(int track); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); + qreal GetItemYFromKeyframeValue(double value); void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); @@ -76,6 +84,8 @@ private: QList bezier_control_points_; + QVector track_visible_; + int track_count_; private slots: diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 4cd9dba3e..da32088ec 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -117,12 +117,17 @@ void CurveWidget::SetInput(NodeInput *input) { if (bridge_) { foreach (QWidget* bridge_widget, bridge_->widgets()) { - delete bridge_widget; + bridge_widget->deleteLater(); } - delete bridge_; + bridge_->deleteLater(); bridge_ = nullptr; } + foreach (QCheckBox* box, checkboxes_) { + box->deleteLater(); + } + checkboxes_.clear(); + if (input_) { disconnect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe); disconnect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe); @@ -142,7 +147,15 @@ void CurveWidget::SetInput(NodeInput *input) for (int i=0;iwidgets().size();i++) { // Insert between two stretches to center the widget - widget_bridge_layout_->insertWidget(2 + i, bridge_->widgets().at(i)); + QCheckBox* checkbox = new QCheckBox(); + checkbox->setChecked(true); + widget_bridge_layout_->insertWidget(2 + i*2, checkbox); + checkboxes_.append(checkbox); + connect(checkbox, &QCheckBox::clicked, this, [this](bool e){ + view_->SetTrackVisible(checkboxes_.indexOf(static_cast(sender())), e); + }); + + widget_bridge_layout_->insertWidget(2 + i*2 + 1, bridge_->widgets().at(i)); } connect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe); @@ -156,6 +169,8 @@ void CurveWidget::SetInput(NodeInput *input) } UpdateInputLabel(); + + QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection); } const double &CurveWidget::GetVerticalScale() @@ -205,8 +220,6 @@ void CurveWidget::ScaleChangedEvent(const double &scale) void CurveWidget::TimeTargetChangedEvent(Node *target) { - ConnectViewerNode(nullptr); - key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); @@ -214,12 +227,11 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) if (bridge_) { bridge_->SetTimeTarget(target); } +} - // FIXME: If a non-viewer node is ever set here, it will fail to update the length - ViewerOutput* viewer = dynamic_cast(target); - if (viewer) { - ConnectViewerNode(viewer); - } +void CurveWidget::ConnectedNodeChanged(ViewerOutput *n) +{ + SetTimeTarget(n); } void CurveWidget::UpdateInputLabel() diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 21e0400d0..7c33b49c7 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -21,6 +21,7 @@ #ifndef CURVEWIDGET_H #define CURVEWIDGET_H +#include #include #include #include @@ -58,6 +59,8 @@ protected: virtual void TimeTargetChangedEvent(Node* target) override; + virtual void ConnectedNodeChanged(ViewerOutput* n) override; + private: void UpdateInputLabel(); @@ -87,6 +90,8 @@ private: NodeParamViewKeyframeControl* key_control_; + QList checkboxes_; + private slots: void SelectionChanged(); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index cfbac331f..2d6b880e9 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -23,7 +23,8 @@ OLIVE_NAMESPACE_ENTER KeyframeView::KeyframeView(QWidget *parent) : - KeyframeViewBase(parent) + KeyframeViewBase(parent), + max_scroll_(0) { setAlignment(Qt::AlignLeft | Qt::AlignTop); } @@ -35,6 +36,12 @@ void KeyframeView::wheelEvent(QWheelEvent *event) } } +void KeyframeView::SceneRectUpdateEvent(QRectF &rect) +{ + rect.setY(0); + rect.setHeight(max_scroll_); +} + void KeyframeView::AddKeyframe(NodeKeyframePtr key, int y) { QPoint global_pt(0, y); diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 505ea4f58..90361c8e5 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -31,12 +31,22 @@ class KeyframeView : public KeyframeViewBase public: KeyframeView(QWidget* parent = nullptr); + void SetMaxScroll(int i) + { + max_scroll_ = i; + } + protected: virtual void wheelEvent(QWheelEvent* event) override; + virtual void SceneRectUpdateEvent(QRectF& rect) override; + public slots: void AddKeyframe(NodeKeyframePtr key, int y); +private: + int max_scroll_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 0bf903b39..c97a85372 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -35,8 +35,6 @@ OLIVE_NAMESPACE_ENTER KeyframeViewBase::KeyframeViewBase(QWidget *parent) : TimelineViewBase(parent), dragging_bezier_point_(nullptr), - y_axis_enabled_(false), - y_scale_(1.0), currently_autoselecting_(false) { SetDefaultDragMode(RubberBandDrag); @@ -57,22 +55,6 @@ void KeyframeViewBase::Clear() item_map_.clear(); } -const double &KeyframeViewBase::GetYScale() const -{ - return y_scale_; -} - -void KeyframeViewBase::SetYScale(const double &y_scale) -{ - y_scale_ = y_scale; - - if (y_axis_enabled_) { - VerticalScaleChangedEvent(y_scale_); - - viewport()->update(); - } -} - void KeyframeViewBase::DeleteSelected() { QUndoCommand* command = new QUndoCommand(); @@ -101,11 +83,20 @@ void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key) KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key) { - KeyframeViewItem* item = new KeyframeViewItem(key); - item->SetTimeTarget(GetTimeTarget()); - item->SetScale(GetScale()); - item_map_.insert(key.get(), item); - scene()->addItem(item); + KeyframeViewItem* item = item_map_.value(key.get()); + + if (!item) { + item = new KeyframeViewItem(key); + item->SetTimeTarget(GetTimeTarget()); + item->SetScale(GetScale()); + item_map_.insert(key.get(), item); + scene()->addItem(item); + + if (hidden_tracks_.contains(key->track())) { + item->setVisible(false); + } + } + return item; } @@ -185,7 +176,7 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) keypair.key->key()->set_time(node_time); - if (y_axis_enabled_) { + if (IsYAxisEnabled()) { keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y()); } @@ -248,7 +239,7 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) command); // Commit value if we're setting a value - if (y_axis_enabled_) { + if (IsYAxisEnabled()) { item->key()->set_value(keypair.value); new NodeParamSetKeyframeValueCommand(item->key(), keypair.value - mouse_diff_scaled.y(), @@ -279,10 +270,6 @@ void KeyframeViewBase::ScaleChangedEvent(const double &scale) } } -void KeyframeViewBase::VerticalScaleChangedEvent(double) -{ -} - const QMap &KeyframeViewBase::item_map() const { return item_map_; @@ -301,9 +288,30 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target) } } -void KeyframeViewBase::SetYAxisEnabled(bool e) +void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) { - y_axis_enabled_ = e; + if (!visible == hidden_tracks_.contains(track)) { + return; + } + + QMap::const_iterator i; + + for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { + if (i.key()->track() == track) { + i.value()->setVisible(visible); + } + } + + if (visible) { + hidden_tracks_.removeOne(track); + } else { + hidden_tracks_.append(track); + } +} + +void KeyframeViewBase::ContextMenuEvent(Menu& m) +{ + Q_UNUSED(m) } rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) @@ -403,7 +411,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include QPointF KeyframeViewBase::GetScaledCursorPos(const QPoint &cursor_pos) { return QPointF(static_cast(cursor_pos.x()) / GetScale(), - static_cast(cursor_pos.y()) / y_scale_); + static_cast(cursor_pos.y()) / GetYScale()); } void KeyframeViewBase::ShowContextMenu() @@ -450,7 +458,11 @@ void KeyframeViewBase::ShowContextMenu() break; } } + } + ContextMenuEvent(m); + + if (!items.isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); @@ -502,7 +514,7 @@ void KeyframeViewBase::ShowKeyframePropertiesDialog() void KeyframeViewBase::AutoSelectKeyTimeNeighbors() { - if (currently_autoselecting_ || y_axis_enabled_) { + if (currently_autoselecting_ || IsYAxisEnabled()) { return; } diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 04e7d6096..743ef996f 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -23,9 +23,10 @@ #include "keyframeviewitem.h" #include "node/keyframe.h" -#include "widget/timetarget/timetarget.h" #include "widget/curvewidget/beziercontrolpointitem.h" +#include "widget/menu/menu.h" #include "widget/timelinewidget/view/timelineviewbase.h" +#include "widget/timetarget/timetarget.h" OLIVE_NAMESPACE_ENTER @@ -37,9 +38,6 @@ public: virtual void Clear(); - const double& GetYScale() const; - void SetYScale(const double& y_scale); - void DeleteSelected(); public slots: @@ -54,15 +52,15 @@ protected: virtual void ScaleChangedEvent(const double& scale) override; - virtual void VerticalScaleChangedEvent(double scale); - const QMap& item_map() const; virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key); virtual void TimeTargetChangedEvent(Node*) override; - void SetYAxisEnabled(bool e); + void SetKeyframeTrackVisible(int track, bool visible); + + virtual void ContextMenuEvent(Menu &m); private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); @@ -94,12 +92,10 @@ private: QVector selected_keys_; - bool y_axis_enabled_; - - double y_scale_; - bool currently_autoselecting_; + QList hidden_tracks_; + private slots: void ShowContextMenu(); diff --git a/app/widget/menu/menu.cpp b/app/widget/menu/menu.cpp index b744c3d32..5d1f7321c 100644 --- a/app/widget/menu/menu.cpp +++ b/app/widget/menu/menu.cpp @@ -50,13 +50,13 @@ Menu::Menu(const QString &s, QWidget *parent) : Init(); } -QAction *Menu::AddActionWithData(const QString &text, const QVariant &data, const QVariant &compare) +QAction *Menu::AddActionWithData(const QString &text, const QVariant &d, const QVariant &compare) { QAction* a = addAction(text); - a->setData(data); + a->setData(d); a->setCheckable(true); - a->setChecked(data == compare); + a->setChecked(d == compare); return a; } diff --git a/app/widget/menu/menu.h b/app/widget/menu/menu.h index edafaf85d..777143efc 100644 --- a/app/widget/menu/menu.h +++ b/app/widget/menu/menu.h @@ -132,7 +132,7 @@ public: } QAction* AddActionWithData(const QString& text, - const QVariant& data, + const QVariant& d, const QVariant& compare); QAction *InsertAlphabetically(const QString& s); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index eaae8d0eb..24f32e7d6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -43,18 +43,21 @@ NodeParamView::NodeParamView(QWidget *parent) : // Set up scroll area for params QScrollArea* scroll_area = new QScrollArea(); + scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll_area->setWidgetResizable(true); splitter->addWidget(scroll_area); // Param widget - QWidget* param_widget_area = new QWidget(); - scroll_area->setWidget(param_widget_area); + param_widget_area_ = new QWidget(); + scroll_area->setWidget(param_widget_area_); // Set up scroll area layout - param_layout_ = new QVBoxLayout(param_widget_area); + param_layout_ = new QVBoxLayout(param_widget_area_); param_layout_->setSpacing(0); - param_layout_->setMargin(0); + + // KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too + param_layout_->setContentsMargins(0, ruler()->height(), 0, 0); // Add a stretch to allow empty space at the bottom of the layout param_layout_->addStretch(); @@ -73,7 +76,6 @@ NodeParamView::NodeParamView(QWidget *parent) : keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ConnectTimelineView(keyframe_view_); connect(keyframe_view_, &KeyframeView::RequestCenterScrollOnPlayhead, this, &NodeParamView::CenterScrollOnPlayhead); - bottom_item_ = keyframe_view_->scene()->addRect(0, 0, 1, 1); keyframe_area_layout->addWidget(keyframe_view_); // Connect ruler and keyframe view together @@ -122,19 +124,15 @@ NodeParamView::NodeParamView(QWidget *parent) : void NodeParamView::SetNodes(QList nodes) { - ConnectViewerNode(nullptr); - // If we already have item widgets, delete them all now foreach (NodeParamViewItem* item, items_) { emit ClosedNode(item->GetNode()); emit FoundGizmos(nullptr); - delete item; + item->deleteLater(); } items_.clear(); - emit TimeTargetChanged(nullptr); // Reset keyframe view - SetTimebase(rational()); keyframe_view_->Clear(); // Set the internal list to the one we've received @@ -158,10 +156,6 @@ void NodeParamView::SetNodes(QList nodes) items_.append(item); - QMetaObject::invokeMethod(item, - "SignalAllKeyframes", - Qt::QueuedConnection); - emit OpenedNode(node); if (!found_gizmos && node->HasGizmos()) { @@ -170,25 +164,9 @@ void NodeParamView::SetNodes(QList nodes) } } - ViewerOutput* viewer = nodes_.first()->FindOutputNode(); + UpdateItemTime(GetTimestamp()); - ConnectViewerNode(viewer); - - if (viewer) { - SetTimebase(viewer->video_params().time_base()); - - // Set viewer as a time target - keyframe_view_->SetTimeTarget(viewer); - - foreach (NodeParamViewItem* item, items_) { - item->SetTimeTarget(viewer); - } - - emit TimeTargetChanged(viewer); - } - - // Forces the scroll to update to this time - keyframe_view_->SetTime(ruler()->GetTime()); + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); } } @@ -211,6 +189,8 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) TimeBasedWidget::TimebaseChangedEvent(timebase); keyframe_view_->SetTimebase(timebase); + + UpdateItemTime(GetTimestamp()); } void NodeParamView::TimeChangedEvent(const int64_t ×tamp) @@ -222,6 +202,16 @@ void NodeParamView::TimeChangedEvent(const int64_t ×tamp) UpdateItemTime(timestamp); } +void NodeParamView::ConnectedNodeChanged(ViewerOutput *n) +{ + // Set viewer as a time target + keyframe_view_->SetTimeTarget(n); + + foreach (NodeParamViewItem* item, items_) { + item->SetTimeTarget(n); + } +} + const QList &NodeParamView::nodes() { return nodes_; @@ -239,7 +229,7 @@ void NodeParamView::DeleteSelected() void NodeParamView::UpdateItemTime(const int64_t ×tamp) { - rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase()); + rational time = Timecode::timestamp_to_time(timestamp, timebase()); foreach (NodeParamViewItem* item, items_) { item->SetTime(time); @@ -251,11 +241,16 @@ void NodeParamView::ItemRequestedTimeChanged(const rational &time) SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); } -void NodeParamView::ForceKeyframeViewToScroll(int min, int max) +void NodeParamView::ForceKeyframeViewToScroll() { - Q_UNUSED(min) + keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height()); +} - bottom_item_->setY(keyframe_view_->viewport()->height() + max); +void NodeParamView::PlaceKeyframesOnView() +{ + foreach (NodeParamViewItem* item, items_) { + QMetaObject::invokeMethod(item, "SignalAllKeyframes", Qt::QueuedConnection); + } } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index fd6fe4220..9fe697d97 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -47,8 +47,6 @@ public: signals: void InputDoubleClicked(NodeInput* input); - void TimeTargetChanged(Node* target); - void RequestSelectNode(const QList& target); void OpenedNode(Node* n); @@ -64,6 +62,8 @@ protected: virtual void TimebaseChangedEvent(const rational&) override; virtual void TimeChangedEvent(const int64_t &) override; + virtual void ConnectedNodeChanged(ViewerOutput* n) override; + private: void UpdateItemTime(const int64_t ×tamp); @@ -77,14 +77,16 @@ private: QScrollBar* vertical_scrollbar_; - QGraphicsRectItem* bottom_item_; - int last_scroll_val_; + QWidget* param_widget_area_; + private slots: void ItemRequestedTimeChanged(const rational& time); - void ForceKeyframeViewToScroll(int min, int max); + void ForceKeyframeViewToScroll(); + + void PlaceKeyframesOnView(); }; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index dc2638ccd..4e4fabba3 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -96,7 +96,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() { IntegerSlider* slider = new IntegerSlider(); slider->SetDefaultValue(input_->GetDefaultValue()); - slider->SetLadderEnabled(true); + slider->SetLadderElementCount(2); widgets_.append(slider); connect(slider, &IntegerSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; @@ -373,7 +373,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count) for (int i=0;iSetDefaultValue(input_->GetDefaultValueForTrack(i)); - fs->SetLadderEnabled(true); + fs->SetLadderElementCount(2); widgets_.append(fs); connect(fs, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); } diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp index f876b82d5..9d6590cbc 100644 --- a/app/widget/nodetableview/nodetableview.cpp +++ b/app/widget/nodetableview/nodetableview.cpp @@ -29,7 +29,8 @@ OLIVE_NAMESPACE_ENTER NodeTableView::NodeTableView(QWidget* parent) : - QTreeWidget(parent) + QTreeWidget(parent), + last_set_node_(nullptr) { setColumnCount(3); setHeaderLabels({tr("Type"), @@ -42,11 +43,23 @@ NodeTableView::NodeTableView(QWidget* parent) : void NodeTableView::SetNode(Node *n, const rational &time) { - clear(); + if (last_set_node_ != n) { + // Clear everything if the node has changed + clear(); + } + last_set_node_ = n; NodeTableTraverser traverser; NodeValueDatabase db = traverser.GenerateDatabase(n, TimeRange(time, time)); + // Remove top items if necessary + for (int i=0;itopLevelItemCount();i++) { + if (!db.contains(this->topLevelItem(i)->data(0, Qt::UserRole).toString())) { + delete this->takeTopLevelItem(i); + i--; + } + } + NodeValueDatabase::const_iterator i; for (i=db.begin(); i!=db.end(); i++) { @@ -58,17 +71,40 @@ void NodeTableView::SetNode(Node *n, const rational &time) continue; } - QTreeWidgetItem* top_item = new QTreeWidgetItem(); - top_item->setText(0, input->name()); - top_item->setFirstColumnSpanned(true); - this->addTopLevelItem(top_item); + QTreeWidgetItem* top_item = nullptr; - for (int j=table.Count()-1; j>=0; j--) { - const NodeValue& value = table.at(j); + for (int j=0;jtopLevelItemCount();j++) { + QTreeWidgetItem* compare = this->topLevelItem(j); + + if (compare->data(0, Qt::UserRole).toString() == input->id()) { + top_item = compare; + break; + } + } + + if (!top_item) { + top_item = new QTreeWidgetItem(); + top_item->setText(0, input->name()); + top_item->setData(0, Qt::UserRole, input->id()); + top_item->setFirstColumnSpanned(true); + this->addTopLevelItem(top_item); + } + + // Create children if necessary + while (top_item->childCount() < table.Count()) { + top_item->addChild(new QTreeWidgetItem()); + } + + // Remove children if necessary + while (top_item->childCount() > table.Count()) { + delete top_item->takeChild(top_item->childCount() - 1); + } + + for (int j=0;jaddChild(sub_item); + QTreeWidgetItem* sub_item = top_item->child(j); // Set data type name sub_item->setText(0, NodeParam::GetPrettyDataTypeName(value.type())); diff --git a/app/widget/nodetableview/nodetableview.h b/app/widget/nodetableview/nodetableview.h index e453903b7..1ce906c09 100644 --- a/app/widget/nodetableview/nodetableview.h +++ b/app/widget/nodetableview/nodetableview.h @@ -36,6 +36,9 @@ public: void SetMultipleNodeMessage(); +private: + Node* last_set_node_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetablewidget.cpp b/app/widget/nodetableview/nodetablewidget.cpp index 20db058c2..0c50794ab 100644 --- a/app/widget/nodetableview/nodetablewidget.cpp +++ b/app/widget/nodetableview/nodetablewidget.cpp @@ -25,7 +25,8 @@ OLIVE_NAMESPACE_ENTER NodeTableWidget::NodeTableWidget(QWidget* parent) : - TimeBasedWidget(parent) + TimeBasedWidget(parent), + node_(nullptr) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); @@ -37,13 +38,35 @@ NodeTableWidget::NodeTableWidget(QWidget* parent) : void NodeTableWidget::SetNodes(const QList &nodes) { + node_ = nullptr; + if (nodes.isEmpty()) { view_->clear(); } else if (nodes.size() == 1) { - view_->SetNode(nodes.first(), rational()); + node_ = nodes.first(); + + ViewerOutput* viewer = node_->FindOutputNode(); + if (viewer) { + qDebug() << "Found timebase"; + SetTimebase(viewer->video_params().time_base()); + } + + UpdateView(); } else { view_->SetMultipleNodeMessage(); } } +void NodeTableWidget::TimeChangedEvent(const int64_t &) +{ + UpdateView(); +} + +void NodeTableWidget::UpdateView() +{ + if (node_) { + view_->SetNode(node_, GetTime()); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetablewidget.h b/app/widget/nodetableview/nodetablewidget.h index ae0c82cc4..bc05d768d 100644 --- a/app/widget/nodetableview/nodetablewidget.h +++ b/app/widget/nodetableview/nodetablewidget.h @@ -33,9 +33,16 @@ public: void SetNodes(const QList& nodes); +protected: + virtual void TimeChangedEvent(const int64_t& ts) override; + private: + void UpdateView(); + NodeTableView* view_; + Node* node_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index 7818e4fd0..727b2aa01 100644 --- a/app/widget/slider/sliderbase.cpp +++ b/app/widget/slider/sliderbase.cpp @@ -40,8 +40,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : require_valid_input_(true), tristate_(false), drag_ladder_(nullptr), - enable_ladder_(false), - ladder_element_count_(2) + ladder_element_count_(0), + dragged_(false) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); @@ -52,9 +52,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : editor_ = new FocusableLineEdit(this); addWidget(editor_); - connect(label_, &SliderLabel::LabelMoved, this, &SliderBase::LabelDragged); - connect(label_, &SliderLabel::LabelReleased, this, &SliderBase::LabelClicked); - connect(label_, &SliderLabel::focused, this, &SliderBase::LabelClicked); + connect(label_, &SliderLabel::LabelPressed, this, &SliderBase::LabelPressed); + connect(label_, &SliderLabel::focused, this, &SliderBase::ShowEditor); connect(label_, &SliderLabel::RequestReset, this, &SliderBase::ResetValue); connect(editor_, &FocusableLineEdit::Confirmed, this, &SliderBase::LineEditConfirmed); connect(editor_, &FocusableLineEdit::Cancelled, this, &SliderBase::LineEditCancelled); @@ -201,6 +200,22 @@ QString SliderBase::GetFormat() const } } +void SliderBase::RepositionLadder() +{ + QPoint label_global_pos = label_->mapToGlobal(label_->pos()); + int text_width = QFontMetricsWidth(label_->fontMetrics(), label_->text()); + QPoint ladder_pos(label_global_pos.x(), + label_global_pos.y() + label_->height() / 2 - drag_ladder_->height() / 2); + + if (ladder_element_count_ > 0) { + ladder_pos.setX(ladder_pos.x() + text_width + QFontMetricsWidth(label_->fontMetrics(), QStringLiteral("H"))); + } else { + ladder_pos.setX(ladder_pos.x() + text_width / 2 - drag_ladder_->width() / 2); + } + + drag_ladder_->move(ladder_pos); +} + void SliderBase::UpdateLabel(const QVariant &v) { if (tristate_) { @@ -226,23 +241,21 @@ QVariant SliderBase::StringToValue(const QString &s, bool *ok) return s; } -void SliderBase::LabelClicked() +void SliderBase::ShowEditor() { - if (!drag_ladder_) { - // This was a simple click - // Load label's text into editor - editor_->setText(ValueToString(value_)); + // This was a simple click + // Load label's text into editor + editor_->setText(ValueToString(value_)); - // Show editor - setCurrentWidget(editor_); + // Show editor + setCurrentWidget(editor_); - // Select all text in the editor - editor_->setFocus(); - editor_->selectAll(); - } + // Select all text in the editor + editor_->setFocus(); + editor_->selectAll(); } -void SliderBase::LabelDragged() +void SliderBase::LabelPressed() { switch (mode_) { case kString: @@ -250,22 +263,24 @@ void SliderBase::LabelDragged() break; case kInteger: case kFloat: - drag_ladder_ = new SliderLadder(drag_multiplier_, enable_ladder_ ? ladder_element_count_ : 0); + { + drag_ladder_ = new SliderLadder(drag_multiplier_, ladder_element_count_); drag_ladder_->SetValue(ValueToString(value_)); drag_ladder_->show(); - QPoint label_global_pos = label_->mapToGlobal(label_->pos()); - drag_ladder_->move(label_global_pos.x() + QFontMetricsWidth(label_->fontMetrics(), label_->text()) / 2 - drag_ladder_->width() / 2, - label_global_pos.y() + label_->height() / 2 - drag_ladder_->height() / 2); + RepositionLadder(); connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &SliderBase::LadderDragged); connect(drag_ladder_, &SliderLadder::Released, this, &SliderBase::LadderReleased); break; } + } } -void SliderBase::LadderDragged(double value, double multiplier) +void SliderBase::LadderDragged(int value, double multiplier) { + dragged_ = true; + switch (mode_) { case kString: // No dragging supported for strings @@ -292,7 +307,10 @@ void SliderBase::LadderDragged(double value, double multiplier) } UpdateLabel(temp_dragged_value_); + drag_ladder_->SetValue(ValueToString(temp_dragged_value_)); + RepositionLadder(); + emit ValueChanged(temp_dragged_value_); break; } @@ -305,20 +323,26 @@ void SliderBase::LadderReleased() drag_ladder_ = nullptr; dragged_diff_ = 0; - // This was a drag - switch (mode_) { - case kString: - // No-op - break; - case kInteger: - SetValue(temp_dragged_value_.toInt()); - break; - case kFloat: - SetValue(temp_dragged_value_.toDouble()); - break; - } + if (dragged_) { + // This was a drag + switch (mode_) { + case kString: + // No-op + break; + case kInteger: + SetValue(temp_dragged_value_.toInt()); + break; + case kFloat: + SetValue(temp_dragged_value_.toDouble()); + break; + } - emit ValueChanged(value_); + emit ValueChanged(value_); + + dragged_ = false; + } else { + ShowEditor(); + } } void SliderBase::LineEditConfirmed() diff --git a/app/widget/slider/sliderbase.h b/app/widget/slider/sliderbase.h index cbc9630a8..7e8062694 100644 --- a/app/widget/slider/sliderbase.h +++ b/app/widget/slider/sliderbase.h @@ -56,11 +56,6 @@ public: void SetFormat(const QString& s); void ClearFormat(); - void SetLadderEnabled(bool e) - { - enable_ladder_ = e; - } - void SetLadderElementCount(int b) { ladder_element_count_ = b; @@ -97,6 +92,8 @@ private: QString GetFormat() const; + void RepositionLadder(); + SliderLabel* label_; FocusableLineEdit* editor_; @@ -124,16 +121,16 @@ private: SliderLadder* drag_ladder_; - bool enable_ladder_; - int ladder_element_count_; + bool dragged_; + private slots: - void LabelClicked(); + void ShowEditor(); - void LabelDragged(); + void LabelPressed(); - void LadderDragged(double value, double multiplier); + void LadderDragged(int value, double multiplier); void LadderReleased(); diff --git a/app/widget/slider/sliderlabel.cpp b/app/widget/slider/sliderlabel.cpp index 8fcf30592..319398a91 100644 --- a/app/widget/slider/sliderlabel.cpp +++ b/app/widget/slider/sliderlabel.cpp @@ -27,8 +27,7 @@ OLIVE_NAMESPACE_ENTER SliderLabel::SliderLabel(QWidget *parent) : - QLabel(parent), - dragging_(false) + QLabel(parent) { QPalette p = palette(); @@ -55,26 +54,10 @@ void SliderLabel::mousePressEvent(QMouseEvent *e) if (e->modifiers() & Qt::AltModifier) { emit RequestReset(); } else { - dragging_ = true; emit LabelPressed(); } } -void SliderLabel::mouseMoveEvent(QMouseEvent *) -{ - if (dragging_) { - emit LabelMoved(); - } -} - -void SliderLabel::mouseReleaseEvent(QMouseEvent *) -{ - if (dragging_) { - emit LabelReleased(); - dragging_ = false; - } -} - void SliderLabel::focusInEvent(QFocusEvent *event) { QWidget::focusInEvent(event); diff --git a/app/widget/slider/sliderlabel.h b/app/widget/slider/sliderlabel.h index 081c5eafa..68d0666e3 100644 --- a/app/widget/slider/sliderlabel.h +++ b/app/widget/slider/sliderlabel.h @@ -36,26 +36,15 @@ public: protected: virtual void mousePressEvent(QMouseEvent *ev) override; - virtual void mouseMoveEvent(QMouseEvent *ev) override; - - virtual void mouseReleaseEvent(QMouseEvent *ev) override; - virtual void focusInEvent(QFocusEvent *event) override; signals: void LabelPressed(); - void LabelMoved(); - - void LabelReleased(); - void focused(); void RequestReset(); -private: - bool dragging_; - }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/slider/sliderladder.cpp b/app/widget/slider/sliderladder.cpp index bbfd50575..5a9c4d235 100644 --- a/app/widget/slider/sliderladder.cpp +++ b/app/widget/slider/sliderladder.cpp @@ -131,14 +131,15 @@ void SliderLadder::TimerUpdate() QCursor::setPos(drag_start_); #endif - int y_threshold = fontMetrics().height() / 2; + if (!x_mvmt && !y_mvmt) { + return; + } - if (qAbs(y_mvmt) > qAbs(x_mvmt) - || qApp->keyboardModifiers() & Qt::ControlModifier) { + if (qApp->keyboardModifiers() & Qt::ControlModifier) { // Movement is vertical y_mobility_ += y_mvmt; - if (qAbs(y_mobility_) > y_threshold) { + if (qAbs(y_mobility_) > fontMetrics().height()) { int new_active_element; if (y_mvmt < 0) { @@ -161,15 +162,9 @@ void SliderLadder::TimerUpdate() y_mobility_ = 0; } } else { - // Movement is horizontal - emit DraggedByValue(x_mvmt , elements_.at(active_element_)->GetMultiplier()); + y_mobility_ = 0; - // Reduce Y mobility - if (y_mobility_ > 0) { - y_mobility_--; - } else if (y_mobility_ < 0) { - y_mobility_++; - } + emit DraggedByValue(x_mvmt + y_mvmt, elements_.at(active_element_)->GetMultiplier()); } } diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index 6c83257d2..5c83206c6 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -36,7 +36,8 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu viewer_node_(nullptr), auto_max_scrollbar_(false), points_(nullptr), - toggle_show_all_(false) + toggle_show_all_(false), + auto_set_timebase_(true) { ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); @@ -79,6 +80,11 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) DisconnectNodeInternal(viewer_node_); disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); + disconnect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase); + + if (auto_set_timebase_) { + SetTimebase(rational()); + } points_ = nullptr; ruler()->ConnectTimelinePoints(nullptr); @@ -95,6 +101,18 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) ruler()->ConnectTimelinePoints(points_); } + if (auto_set_timebase_) { + if (!viewer_node_->video_params().time_base().isNull()) { + SetTimebase(viewer_node_->video_params().time_base()); + } else if (viewer_node_->audio_params().sample_rate() > 0) { + SetTimebase(viewer_node_->audio_params().time_base()); + } else { + SetTimebase(rational()); + } + + connect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase); + } + ConnectNodeInternal(viewer_node_); } @@ -332,6 +350,11 @@ void TimeBasedWidget::CenterScrollOnPlayhead() scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2); } +void TimeBasedWidget::SetAutoSetTimebase(bool e) +{ + auto_set_timebase_ = e; +} + void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) { if (!points_) { @@ -459,12 +482,12 @@ void TimeBasedWidget::ToggleShowAll() w = timeline_views_.first()->width(); } - w = w / 10 * 9; + toggle_show_all_old_scale_ = GetScale(); toggle_show_all_old_scroll_ = scrollbar_->value(); - SetScale(w / GetConnectedNode()->GetLength().toDouble()); + SetScaleFromDimensions(w, GetConnectedNode()->GetLength().toDouble()); scrollbar_->setValue(0); // Must explicitly do this because SetScale() will automatically set this to false diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h index 49012af1c..008514d26 100644 --- a/app/widget/timebased/timebased.h +++ b/app/widget/timebased/timebased.h @@ -52,6 +52,8 @@ public: void SetScaleAndCenterOnPlayhead(const double& scale); + TimeRuler* ruler() const; + public slots: void SetTimestamp(int64_t timestamp); @@ -89,8 +91,6 @@ public slots: void GoToOut(); - TimeRuler* ruler() const; - protected slots: void SetTimeAndSignal(const int64_t& t); @@ -127,6 +127,12 @@ protected slots: */ void CenterScrollOnPlayhead(); + /** + * @brief By default, TimeBasedWidget will set the timebase to the viewer node's video timebase. + * Set this to false if you want to set your own timebase. + */ + void SetAutoSetTimebase(bool e); + signals: void TimeChanged(const int64_t&); @@ -170,6 +176,8 @@ private: double toggle_show_all_old_scale_; int toggle_show_all_old_scroll_; + bool auto_set_timebase_; + private slots: void UpdateMaximumScroll(); diff --git a/app/widget/timelinewidget/timelinescaledobject.cpp b/app/widget/timelinewidget/timelinescaledobject.cpp index 979ecf048..d12a998f8 100644 --- a/app/widget/timelinewidget/timelinescaledobject.cpp +++ b/app/widget/timelinewidget/timelinescaledobject.cpp @@ -27,6 +27,8 @@ OLIVE_NAMESPACE_ENTER +const int TimelineScaledObject::kCalculateDimensionsPadding = 10; + TimelineScaledObject::TimelineScaledObject() : scale_(1.0), min_scale_(0), @@ -112,6 +114,21 @@ void TimelineScaledObject::SetScale(const double& scale) ScaleChangedEvent(scale_); } +void TimelineScaledObject::SetScaleFromDimensions(double viewport_width, double content_width) +{ + SetScale(CalculateScaleFromDimensions(viewport_width, content_width)); +} + +double TimelineScaledObject::CalculateScaleFromDimensions(double viewport_sz, double content_sz) +{ + return static_cast(viewport_sz / kCalculateDimensionsPadding * (kCalculateDimensionsPadding-1)) / static_cast(content_sz); +} + +double TimelineScaledObject::CalculatePaddingFromDimensionScale(double viewport_sz) +{ + return (viewport_sz / (kCalculateDimensionsPadding * 2)); +} + TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) : QWidget(parent) { diff --git a/app/widget/timelinewidget/timelinescaledobject.h b/app/widget/timelinewidget/timelinescaledobject.h index c55cff296..c4aec301c 100644 --- a/app/widget/timelinewidget/timelinescaledobject.h +++ b/app/widget/timelinewidget/timelinescaledobject.h @@ -44,6 +44,10 @@ public: void SetScale(const double& scale); + void SetScaleFromDimensions(double viewport_width, double content_width); + static double CalculateScaleFromDimensions(double viewport_sz, double content_sz); + static double CalculatePaddingFromDimensionScale(double viewport_sz); + protected: double TimeToScene(const rational& time); rational SceneToTime(const double &x, bool round = false); @@ -67,6 +71,8 @@ private: double max_scale_; + static const int kCalculateDimensionsPadding; + }; class TimelineScaledWidget : public QWidget, public TimelineScaledObject diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 908d7e3a2..504293a75 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -147,8 +147,10 @@ TimelineWidget::TimelineWidget(QWidget *parent) : view_splitter->setSizes({INT_MAX, INT_MAX}); // FIXME: Magic number - SetMaximumScale(TimelineViewBase::kMaximumScale); SetScale(90.0); + + SetMaximumScale(TimelineViewBase::kMaximumScale); + SetAutoSetTimebase(false); } TimelineWidget::~TimelineWidget() @@ -1584,9 +1586,9 @@ bool TimelineWidget::SnapPoint(QList start_times, rational* movement, // Find all points at this movement QList snap_times; - foreach (const SnapData& data, potential_snaps) { - if (data.movement == *movement) { - snap_times.append(data.time); + foreach (const SnapData& d, potential_snaps) { + if (d.movement == *movement) { + snap_times.append(d.time); } } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 2d3cc4511..d3ae73c56 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -45,7 +45,6 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setBackgroundRole(QPalette::Window); setContextMenuPolicy(Qt::CustomContextMenu); - SetLimitYAxis(true); viewport()->setMouseTracking(true); connect(scene(), &QGraphicsScene::selectionChanged, this, &TimelineView::SelectionChanged); diff --git a/app/widget/timelinewidget/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index c945f80db..a5acb99e1 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -39,9 +39,10 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : playhead_scene_left_(-1), playhead_scene_right_(-1), dragging_playhead_(false), - limit_y_axis_(false), snapped_(false), - snap_service_(nullptr) + snap_service_(nullptr), + y_axis_enabled_(false), + y_scale_(1.0) { setScene(&scene_); @@ -81,6 +82,26 @@ void TimelineViewBase::SetSnapService(SnapService *service) snap_service_ = service; } +const double &TimelineViewBase::GetYScale() const +{ + return y_scale_; +} + +void TimelineViewBase::VerticalScaleChangedEvent(double) +{ +} + +void TimelineViewBase::SetYScale(const double &y_scale) +{ + y_scale_ = y_scale; + + if (y_axis_enabled_) { + VerticalScaleChangedEvent(y_scale_); + + viewport()->update(); + } +} + void TimelineViewBase::SetTime(const int64_t time) { playhead_ = time; @@ -253,10 +274,56 @@ bool TimelineViewBase::HandleZoomFromScroll(QWheelEvent *event) if (WheelEventIsAZoomEvent(event)) { // If CTRL is held (or a preference is set to swap CTRL behavior), we zoom instead of scrolling if (!event->angleDelta().isNull()) { - if (event->angleDelta().x() + event->angleDelta().y() > 0) { - emit ScaleChanged(GetScale() * 1.1); + bool only_vertical = false; + bool only_horizontal = false; + + // Ctrl+Shift limits to only one axis + // Alt switches between horizontal only (alt held) or vertical only (alt not held) + if (y_axis_enabled_) { + if (event->modifiers() & Qt::ShiftModifier) { + if (event->modifiers() & Qt::AltModifier) { + only_horizontal = true; + } else { + only_vertical = true; + } + } } else { - emit ScaleChanged(GetScale() * 0.9); + only_horizontal = true; + } + + double scale_multiplier; + + if (event->angleDelta().x() + event->angleDelta().y() > 0) { + scale_multiplier = 1.1; + } else { + scale_multiplier = 0.9; + } + + QPointF cursor_pos; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + cursor_pos = event->position(); +#else + cursor_pos = event->posF(); +#endif + + if (!only_vertical) { + double new_x_scale = GetScale() * scale_multiplier; + + int new_x_scroll = qRound(horizontalScrollBar()->value() / GetScale() * new_x_scale + (cursor_pos.x() - cursor_pos.x() / new_x_scale * GetScale())); + + emit ScaleChanged(new_x_scale); + + horizontalScrollBar()->setValue(new_x_scroll); + } + + if (!only_horizontal) { + double new_y_scale = GetYScale() * scale_multiplier; + + int new_y_scroll = qRound(verticalScrollBar()->value() / GetYScale() * new_y_scale + (cursor_pos.y() - cursor_pos.y() / new_y_scale * GetYScale())); + + SetYScale(new_y_scale); + + verticalScrollBar()->setValue(new_y_scroll); } } @@ -271,10 +338,4 @@ bool TimelineViewBase::WheelEventIsAZoomEvent(QWheelEvent *event) return (static_cast(event->modifiers() & Qt::ControlModifier) == !Config::Current()["ScrollZooms"].toBool()); } -void TimelineViewBase::SetLimitYAxis(bool) -{ - limit_y_axis_ = true; - UpdateSceneRect(); -} - OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/view/timelineviewbase.h b/app/widget/timelinewidget/view/timelineviewbase.h index e664f5178..d24af659e 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.h +++ b/app/widget/timelinewidget/view/timelineviewbase.h @@ -48,6 +48,9 @@ public: void SetSnapService(SnapService* service); + const double& GetYScale() const; + void SetYScale(const double& y_scale); + public slots: void SetTime(const int64_t time); @@ -69,12 +72,12 @@ protected: virtual void SceneRectUpdateEvent(QRectF&){} + virtual void VerticalScaleChangedEvent(double scale); + bool HandleZoomFromScroll(QWheelEvent* event); bool WheelEventIsAZoomEvent(QWheelEvent* event); - void SetLimitYAxis(bool e); - rational GetPlayheadTime() const; bool PlayheadPress(QMouseEvent* event); @@ -83,6 +86,16 @@ protected: virtual void TimebaseChangedEvent(const rational &) override; + bool IsYAxisEnabled() const + { + return y_axis_enabled_; + } + + void SetYAxisEnabled(bool e) + { + y_axis_enabled_ = e; + } + private: qreal GetPlayheadX(); @@ -97,8 +110,6 @@ private: QGraphicsScene scene_; - bool limit_y_axis_; - bool snapped_; QList snap_time_; @@ -106,6 +117,10 @@ private: SnapService* snap_service_; + bool y_axis_enabled_; + + double y_scale_; + private slots: /** * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 6280ad88f..77ffb8102 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -168,15 +168,6 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) { - if (!n->video_params().time_base().isNull()) { - SetTimebase(n->video_params().time_base()); - } else if (n->audio_params().sample_rate() > 0) { - SetTimebase(n->audio_params().time_base()); - } else { - SetTimebase(rational()); - } - - connect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase); connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); connect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); @@ -231,9 +222,6 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) } cache_wait_timer_.stop(); - SetTimebase(rational()); - - disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase); disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); disconnect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); diff --git a/app/window/mainwindow/CMakeLists.txt b/app/window/mainwindow/CMakeLists.txt index 151883684..4b70b8f9e 100644 --- a/app/window/mainwindow/CMakeLists.txt +++ b/app/window/mainwindow/CMakeLists.txt @@ -22,5 +22,7 @@ set(OLIVE_SOURCES window/mainwindow/mainstatusbar.cpp window/mainwindow/mainwindow.h window/mainwindow/mainwindow.cpp + window/mainwindow/mainwindowlayoutinfo.h + window/mainwindow/mainwindowlayoutinfo.cpp PARENT_SCOPE ) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index a917143f6..1c5f2b111 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -86,7 +86,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(node_panel_, &NodePanel::SelectionChanged, table_panel_, &NodeTablePanel::SetNodes); connect(param_panel_, &ParamPanel::RequestSelectNode, node_panel_, &NodePanel::Select); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); + connect(param_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); connect(param_panel_, &ParamPanel::FoundGizmos, sequence_viewer_panel_, &SequenceViewerPanel::SetGizmos); connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); @@ -109,43 +111,41 @@ MainWindow::~MainWindow() #endif } -void MainWindow::LoadLayout(QXmlStreamReader *reader, XMLNodeData &xml_data) +void MainWindow::LoadLayout(const MainWindowLayoutInfo &info) { - QMetaObject::invokeMethod(this, - "LoadLayoutInternal", - Qt::BlockingQueuedConnection, - Q_ARG(QXmlStreamReader*, reader), - Q_ARG(XMLNodeData*, &xml_data)); + foreach (Folder* folder, info.open_folders()) { + FolderOpen(folder->project(), folder, true); + } + + foreach (Sequence* sequence, info.open_sequences()) { + OpenSequence(sequence, info.open_sequences().size() == 1); + } + + restoreState(info.state()); } -void MainWindow::SaveLayout(QXmlStreamWriter *writer) const +MainWindowLayoutInfo MainWindow::SaveLayout() const { - writer->writeStartElement(QStringLiteral("layout")); - - writer->writeStartElement(QStringLiteral("folders")); + MainWindowLayoutInfo info; foreach (ProjectPanel* panel, folder_panels_) { - writer->writeTextElement(QStringLiteral("folder"), - QString::number(reinterpret_cast(panel->get_root_index().internalPointer()))); + if (panel->project()) { + info.add_folder(static_cast(panel->get_root_index().internalPointer())); + } } - writer->writeEndElement(); // folders - - writer->writeStartElement(QStringLiteral("timeline")); - foreach (TimelinePanel* panel, timeline_panels_) { - writer->writeTextElement(QStringLiteral("sequence"), - QString::number(reinterpret_cast(panel->GetConnectedViewer()))); + if (panel->GetConnectedViewer()) { + info.add_sequence(static_cast(panel->GetConnectedViewer()->parent())); + } } - writer->writeEndElement(); // timeline + info.set_state(saveState()); - writer->writeTextElement(QStringLiteral("state"), QString(saveState().toBase64())); - - writer->writeEndElement(); // layout + return info; } -void MainWindow::OpenSequence(Sequence *sequence) +void MainWindow::OpenSequence(Sequence *sequence, bool enable_focus) { // See if this sequence is already open, and switch to it if so foreach (TimelinePanel* tl, timeline_panels_) { @@ -162,11 +162,14 @@ void MainWindow::OpenSequence(Sequence *sequence) panel = timeline_panels_.first(); } else { panel = AppendTimelinePanel(); + enable_focus = false; } panel->ConnectViewerNode(sequence->viewer_output()); - TimelineFocused(sequence->viewer_output()); + if (enable_focus) { + TimelineFocused(sequence->viewer_output()); + } } void MainWindow::CloseSequence(Sequence *sequence) @@ -465,59 +468,13 @@ void MainWindow::FloatingPanelCloseRequested() panel->deleteLater(); } -void MainWindow::LoadLayoutInternal(QXmlStreamReader *reader, XMLNodeData *xml_data) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("folders")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("folder")) { - quintptr item_id = reader->readElementText().toULongLong(); - - Item* open_item = xml_data->item_ptrs.value(item_id); - - if (open_item) { - FolderOpen(open_item->project(), open_item, true); - } - } else { - reader->skipCurrentElement(); - } - } - - } else if (reader->name() == QStringLiteral("timeline")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("sequence")) { - quintptr item_id = reader->readElementText().toULongLong(); - - Sequence* open_seq = dynamic_cast(xml_data->item_ptrs.value(item_id)); - - if (open_seq) { - OpenSequence(open_seq); - } - } else { - reader->skipCurrentElement(); - } - } - - } else if (reader->name() == QStringLiteral("state")) { - - QByteArray state = QByteArray::fromBase64(reader->readElementText().toLatin1()); - - restoreState(state); - - } else { - reader->skipCurrentElement(); - } - } -} - TimelinePanel* MainWindow::AppendTimelinePanel() { TimelinePanel* panel = AppendPanelInternal(timeline_panels_); connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested); connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); + connect(panel, &TimelinePanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); connect(panel, &TimelinePanel::SelectionChanged, node_panel_, &NodePanel::SelectBlocks); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); @@ -565,6 +522,7 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel) void MainWindow::TimelineFocused(ViewerOutput* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); + param_panel_->ConnectViewerNode(viewer); Sequence* seq = nullptr; diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index c9f349051..f06b8c0fe 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -23,6 +23,7 @@ #include +#include "mainwindowlayoutinfo.h" #include "panel/panelmanager.h" #include "panel/audiomonitor/audiomonitor.h" #include "panel/curve/curve.h" @@ -55,11 +56,11 @@ public: virtual ~MainWindow() override; - void LoadLayout(QXmlStreamReader* reader, XMLNodeData& xml_data); + void LoadLayout(const MainWindowLayoutInfo &info); - void SaveLayout(QXmlStreamWriter* writer) const; + MainWindowLayoutInfo SaveLayout() const; - void OpenSequence(Sequence* sequence); + void OpenSequence(Sequence* sequence, bool enable_focus = true); void CloseSequence(Sequence* sequence); @@ -167,8 +168,6 @@ private slots: void FloatingPanelCloseRequested(); - void LoadLayoutInternal(QXmlStreamReader* reader, XMLNodeData *xml_data); - void StatusBarDoubleClicked(); #ifdef Q_OS_LINUX diff --git a/app/window/mainwindow/mainwindowlayoutinfo.cpp b/app/window/mainwindow/mainwindowlayoutinfo.cpp new file mode 100644 index 000000000..168978f79 --- /dev/null +++ b/app/window/mainwindow/mainwindowlayoutinfo.cpp @@ -0,0 +1,96 @@ +#include "mainwindowlayoutinfo.h" + +OLIVE_NAMESPACE_ENTER + +void MainWindowLayoutInfo::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("layout")); + + writer->writeStartElement(QStringLiteral("folders")); + + foreach (Folder* folder, open_folders_) { + writer->writeTextElement(QStringLiteral("folder"), + QString::number(reinterpret_cast(folder))); + } + + writer->writeEndElement(); // folders + + writer->writeStartElement(QStringLiteral("timeline")); + + foreach (Sequence* sequence, open_sequences_) { + writer->writeTextElement(QStringLiteral("sequence"), + QString::number(reinterpret_cast(sequence))); + } + + writer->writeEndElement(); // timeline + + writer->writeTextElement(QStringLiteral("state"), QString(state_.toBase64())); + + writer->writeEndElement(); // layout +} + +MainWindowLayoutInfo MainWindowLayoutInfo::fromXml(QXmlStreamReader *reader, XMLNodeData &xml_data) +{ + MainWindowLayoutInfo info; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("folders")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("folder")) { + quintptr item_id = reader->readElementText().toULongLong(); + + Item* open_item = xml_data.item_ptrs.value(item_id); + + if (open_item) { + info.open_folders_.append(static_cast(open_item)); + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("timeline")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("sequence")) { + quintptr item_id = reader->readElementText().toULongLong(); + + Sequence* open_seq = dynamic_cast(xml_data.item_ptrs.value(item_id)); + + if (open_seq) { + info.open_sequences_.append(open_seq); + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("state")) { + + info.state_ = QByteArray::fromBase64(reader->readElementText().toLatin1()); + + } else { + reader->skipCurrentElement(); + } + } + + return info; +} + +void MainWindowLayoutInfo::add_folder(olive::Folder *f) +{ + open_folders_.append(f); +} + +void MainWindowLayoutInfo::add_sequence(Sequence *s) +{ + open_sequences_.append(s); +} + +void MainWindowLayoutInfo::set_state(const QByteArray &layout) +{ + state_ = layout; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/window/mainwindow/mainwindowlayoutinfo.h b/app/window/mainwindow/mainwindowlayoutinfo.h new file mode 100644 index 000000000..5f76cbc2b --- /dev/null +++ b/app/window/mainwindow/mainwindowlayoutinfo.h @@ -0,0 +1,52 @@ +#ifndef MAINWINDOWLAYOUTINFO_H +#define MAINWINDOWLAYOUTINFO_H + +#include "project/item/folder/folder.h" +#include "project/item/sequence/sequence.h" + +OLIVE_NAMESPACE_ENTER + +class MainWindowLayoutInfo +{ +public: + MainWindowLayoutInfo() = default; + + void toXml(QXmlStreamWriter* writer) const; + + static MainWindowLayoutInfo fromXml(QXmlStreamReader* reader, XMLNodeData &xml_data); + + void add_folder(Folder* f); + + void add_sequence(Sequence* s); + + void set_state(const QByteArray& layout); + + const QList& open_folders() const + { + return open_folders_; + } + + const QList& open_sequences() const + { + return open_sequences_; + } + + const QByteArray& state() const + { + return state_; + } + +private: + QByteArray state_; + + QList open_folders_; + + QList open_sequences_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::MainWindowLayoutInfo) + +#endif // MAINWINDOWLAYOUTINFO_H