diff --git a/app/core.cpp b/app/core.cpp index 6b0cebd5c..e29811114 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -396,9 +396,6 @@ void Core::CreateNewSequence() // Create new sequence Sequence* new_sequence = CreateNewSequenceForProject(active_project); - // Set all defaults for the sequence - new_sequence->set_default_parameters(); - SequenceDialog sd(new_sequence, SequenceDialog::kNew, main_window_); // Make sure SequenceDialog doesn't make an undo command for editing the sequence, since we make an undo command for @@ -1091,6 +1088,11 @@ void Core::OpenRecoveryProject(const QString &filename) OpenProjectInternal(filename, true); } +void Core::OpenNodeInViewer(ViewerOutput *viewer) +{ + main_window_->OpenNodeInViewer(viewer); +} + void Core::CheckForAutoRecoveries() { QFile autorecovery_index(GetAutoRecoveryIndexFilename()); diff --git a/app/core.h b/app/core.h index 4824a275d..38051c56b 100644 --- a/app/core.h +++ b/app/core.h @@ -300,6 +300,8 @@ public: void OpenRecoveryProject(const QString& filename); + void OpenNodeInViewer(ViewerOutput* viewer); + static const uint kProjectVersion; public slots: diff --git a/app/node/graph.cpp b/app/node/graph.cpp index f76f390b4..6e5055903 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -61,6 +61,7 @@ void NodeGraph::childEvent(QChildEvent *event) connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); emit NodeAdded(node); + emit node->AddedToGraph(this); } else if (event->type() == QEvent::ChildRemoved) { @@ -72,6 +73,7 @@ void NodeGraph::childEvent(QChildEvent *event) disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); emit NodeRemoved(node); + emit node->RemovedFromGraph(this); } } diff --git a/app/node/node.h b/app/node/node.h index 86e4d2f54..74d1b1fd8 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -926,6 +926,10 @@ signals: void InputDataTypeChanged(const QString& id, NodeValue::Type type); + void AddedToGraph(NodeGraph* graph); + + void RemovedFromGraph(NodeGraph* graph); + private: class ArrayInsertCommand : public UndoCommand { diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 4dd6eebd7..271fbf36d 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -160,13 +160,15 @@ public: static Type TypeFromString(const QString& s) { - if (s.at(1) == ':') { - if (s.at(0) == 'v') { - // Video stream - return Track::kVideo; - } else if (s.at(0) == 'a') { - // Audio stream - return Track::kAudio; + if (s.size() >= 3) { + if (s.at(1) == ':') { + if (s.at(0) == 'v') { + // Video stream + return Track::kVideo; + } else if (s.at(0) == 'a') { + // Audio stream + return Track::kAudio; + } } } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 81c89b383..3543e6fdd 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -54,6 +54,7 @@ ViewerOutput::ViewerOutput(bool create_default_streams) : if (create_default_streams) { AddStream(Track::kVideo, QVariant()); AddStream(Track::kAudio, QVariant()); + set_default_parameters(); } } @@ -302,39 +303,19 @@ void ViewerOutput::Retranslate() void ViewerOutput::VerifyLength() { - NodeTraverser traverser; - rational video_length, audio_length, subtitle_length; - { - video_length = GetCustomLength(Track::kVideo); - - if (video_length.isNull() && IsInputConnected(kTextureInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); - video_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); - } - - if (cache_enabled_) { - video_frame_cache_.SetLength(video_length); - } + video_length = VerifyLengthInternal(Track::kVideo); + if (cache_enabled_) { + video_frame_cache_.SetLength(video_length); } - { - audio_length = GetCustomLength(Track::kAudio); - - if (audio_length.isNull() && IsInputConnected(kSamplesInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); - audio_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); - } - - if (cache_enabled_) { - audio_playback_cache_.SetLength(audio_length); - } + audio_length = VerifyLengthInternal(Track::kAudio); + if (cache_enabled_) { + audio_playback_cache_.SetLength(audio_length); } - { - subtitle_length = GetCustomLength(Track::kSubtitle); - } + subtitle_length = VerifyLengthInternal(Track::kSubtitle); rational real_length = qMax(subtitle_length, qMax(video_length, audio_length)); @@ -362,9 +343,30 @@ void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, con super::InputDisconnectedEvent(input, element, output); } -rational ViewerOutput::GetCustomLength(Track::Type type) const +rational ViewerOutput::VerifyLengthInternal(Track::Type type) const { - Q_UNUSED(type) + NodeTraverser traverser; + + switch (type) { + case Track::kVideo: + if (IsInputConnected(kTextureInput)) { + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); + qDebug() << "Got video length:" << t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + return t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + } + break; + case Track::kAudio: + if (IsInputConnected(kSamplesInput)) { + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); + return t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + } + break; + case Track::kNone: + case Track::kSubtitle: + case Track::kCount: + break; + } + return rational(); } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index a56602b49..a7fc6dc06 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -174,7 +174,7 @@ protected: virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; - virtual rational GetCustomLength(Track::Type type) const; + virtual rational VerifyLengthInternal(Track::Type type) const; virtual void ShiftVideoEvent(const rational &from, const rational &to); diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 8344cca0d..fef4cf4fb 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -180,7 +180,7 @@ void Footage::InputValueChangedEvent(const QString &input, int element) } } -rational Footage::GetCustomLength(Track::Type type) const +rational Footage::VerifyLengthInternal(Track::Type type) const { if (type == Track::kVideo) { VideoParams first_stream = GetFirstEnabledVideoStream(); @@ -196,7 +196,7 @@ rational Footage::GetCustomLength(Track::Type type) const } } - return super::GetCustomLength(type); + return super::VerifyLengthInternal(type); } QString Footage::GetColorspaceToUse(const VideoParams ¶ms) const diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 153880987..bed9a1d63 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -58,7 +58,7 @@ public: virtual QString Name() const override { - return tr("Footage"); + return tr("Media"); } virtual QString id() const override @@ -195,7 +195,7 @@ protected: virtual void InputValueChangedEvent(const QString &input, int element) override; - virtual rational GetCustomLength(Track::Type type) const override; + virtual rational VerifyLengthInternal(Track::Type type) const override; private: QString GetColorspaceToUse(const VideoParams& params) const; diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index 08c0b773f..9804b74a9 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -430,9 +430,6 @@ void ProjectViewModel::ConnectItem(Node *n) connect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem); connect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem); - connect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::ItemAdded); - connect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::ItemRemoved); - foreach (Node* c, f->children()) { ConnectItem(c); } @@ -450,9 +447,6 @@ void ProjectViewModel::DisconnectItem(Node *n) disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem); disconnect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem); - disconnect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::ItemAdded); - disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::ItemRemoved); - foreach (Node* c, f->children()) { DisconnectItem(c); } diff --git a/app/node/project/projectviewmodel.h b/app/node/project/projectviewmodel.h index 1e396746a..9a57c80da 100644 --- a/app/node/project/projectviewmodel.h +++ b/app/node/project/projectviewmodel.h @@ -104,11 +104,6 @@ public: */ QModelIndex CreateIndexFromItem(Node *item, int column = 0); -signals: - void ItemAdded(Node* node); - - void ItemRemoved(Node* node); - private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index b6c2f1653..3e7659dd7 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -107,7 +107,7 @@ void Sequence::Retranslate() } } -rational Sequence::GetCustomLength(Track::Type type) const +rational Sequence::VerifyLengthInternal(Track::Type type) const { if (!track_lists_.isEmpty()) { switch (type) { diff --git a/app/node/project/sequence/sequence.h b/app/node/project/sequence/sequence.h index c644bdc3f..6b2c1d0ac 100644 --- a/app/node/project/sequence/sequence.h +++ b/app/node/project/sequence/sequence.h @@ -103,7 +103,7 @@ protected: virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; - virtual rational GetCustomLength(Track::Type type) const override; + virtual rational VerifyLengthInternal(Track::Type type) const override; signals: void TrackAdded(Track* track); diff --git a/app/node/value.cpp b/app/node/value.cpp index 6ab6cd5cf..f3ec8655c 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -382,14 +382,26 @@ NodeValueTable NodeValueTable::Merge(QList tables) NodeValueTable merged_table; // Slipstreams all tables together - foreach (const NodeValueTable& t, tables) { - if (row >= t.Count()) { - continue; + while (true) { + bool all_merged = true; + + foreach (const NodeValueTable& t, tables) { + if (row < t.Count()) { + all_merged = false; + } else { + continue; + } + + int row_index = t.Count() - 1 - row; + + merged_table.Prepend(t.at(row_index)); } - int row_index = t.Count() - 1 - row; + row++; - merged_table.Prepend(t.at(row_index)); + if (all_merged) { + break; + } } return merged_table; diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index c580d4f14..a56e2ea82 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -57,7 +57,6 @@ ProjectPanel::ProjectPanel(QWidget *parent) : explorer_ = new ProjectExplorer(this); layout->addWidget(explorer_); connect(explorer_, &ProjectExplorer::DoubleClickedItem, this, &ProjectPanel::ItemDoubleClickSlot); - connect(explorer_, &ProjectExplorer::ItemRemoved, this, &ProjectPanel::ItemRemoved); // Set toolbar's view to the explorer's view toolbar->SetView(explorer_->view_type()); @@ -233,16 +232,6 @@ void ProjectPanel::SaveConnectedProject() Core::instance()->SaveProject(this->project()); } -void ProjectPanel::ItemRemoved(Node *item) -{ - // Open this footage in a FootageViewer - FootageViewerPanel* panel = PanelManager::instance()->MostRecentlyFocused(); - - if (panel->GetConnectedViewer() == item) { - panel->DisconnectViewerNode(); - } -} - QVector ProjectPanel::GetSelectedFootage() const { QVector items = SelectedItems(); diff --git a/app/panel/project/project.h b/app/panel/project/project.h index c16c36bd7..c8a8c2aef 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -80,8 +80,6 @@ private slots: void SaveConnectedProject(); - void ItemRemoved(Node* item); - }; } diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index a79f09b52..f5faf418c 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -25,13 +25,13 @@ namespace olive { ViewerPanel::ViewerPanel(const QString &object_name, QWidget *parent) : ViewerPanelBase(object_name, parent) { - // Set ViewerWidget as the central widget - ViewerWidget* vw = new ViewerWidget(); - connect(vw, &ViewerWidget::RequestScopePanel, this, &ViewerPanel::CreateScopePanel); - SetTimeBasedWidget(vw); + Init(); +} - // Set strings - Retranslate(); +ViewerPanel::ViewerPanel(QWidget *parent) : + ViewerPanelBase(QStringLiteral("ViewerPanel"), parent) +{ + Init(); } void ViewerPanel::Retranslate() @@ -41,4 +41,15 @@ void ViewerPanel::Retranslate() SetTitle(tr("Viewer")); } +void ViewerPanel::Init() +{ + // Set ViewerWidget as the central widget + ViewerWidget* vw = new ViewerWidget(); + connect(vw, &ViewerWidget::RequestScopePanel, this, &ViewerPanel::CreateScopePanel); + SetTimeBasedWidget(vw); + + // Set strings + Retranslate(); +} + } diff --git a/app/panel/viewer/viewer.h b/app/panel/viewer/viewer.h index 9d83e0c97..3de6172d3 100644 --- a/app/panel/viewer/viewer.h +++ b/app/panel/viewer/viewer.h @@ -34,10 +34,14 @@ class ViewerPanel : public ViewerPanelBase { Q_OBJECT public: ViewerPanel(const QString& object_name, QWidget* parent); + ViewerPanel(QWidget* parent); protected: virtual void Retranslate() override; +private: + void Init(); + }; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 94028868b..7582ac5d2 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -668,6 +668,13 @@ void NodeView::ShowContextMenu(const QPoint &pos) QAction* autopos = m.addAction(tr("Auto-Position")); connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents); + ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode()); + if (viewer) { + m.addSeparator(); + QAction* open_in_viewer_action = m.addAction(tr("Open in Viewer")); + connect(open_in_viewer_action, &QAction::triggered, this, &NodeView::OpenSelectedNodeInViewer); + } + } else { QAction* curved_action = m.addAction(tr("Smooth Edges")); @@ -756,6 +763,16 @@ void NodeView::ContextMenuFilterChanged(QAction *action) Q_UNUSED(action) } +void NodeView::OpenSelectedNodeInViewer() +{ + QVector selected = scene_.GetSelectedNodes(); + ViewerOutput* viewer = selected.isEmpty() ? nullptr : dynamic_cast(selected.first()); + + if (viewer) { + Core::instance()->OpenNodeInViewer(viewer); + } +} + void NodeView::AttachNodesToCursor(const QVector &nodes) { QVector items(nodes.size()); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 792d697b2..e01376419 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -194,6 +194,11 @@ private slots: */ void ContextMenuFilterChanged(QAction* action); + /** + * @brief Opens the selected node in a Viewer + */ + void OpenSelectedNodeInViewer(); + }; } diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 3c870caba..dddf8abff 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -95,8 +95,6 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : connect(tree_view_, &ProjectExplorerTreeView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); connect(list_view_, &ProjectExplorerListView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); connect(icon_view_, &ProjectExplorerIconView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); - - connect(&model_, &ProjectViewModel::ItemRemoved, this, &ProjectExplorer::ItemRemoved); } const ProjectToolbar::ViewType &ProjectExplorer::view_type() const diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 190faa510..5602a7e23 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -100,8 +100,6 @@ signals: */ void DoubleClickedItem(Node* item); - void ItemRemoved(Node* node); - private: /** * @brief Get all the blocks that solely rely on an input node diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index d65ccacfc..34c2a6e6f 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -85,6 +85,7 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) // Disconnect length changed signal disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); + disconnect(viewer_node_, &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); // Disconnect rate change signals if they were connected disconnect(viewer_node_, &ViewerOutput::FrameRateChanged, this, &TimeBasedWidget::AutoUpdateTimebase); @@ -109,6 +110,7 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) if (viewer_node_) { // Connect length changed signal connect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); + connect(viewer_node_, &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); // Connect ruler and scrollbar to timeline points ruler()->ConnectTimelinePoints(viewer_node_->GetTimelinePoints()); @@ -216,6 +218,11 @@ void TimeBasedWidget::AutoUpdateTimebase() } } +void TimeBasedWidget::ConnectedNodeRemovedFromGraph() +{ + ConnectViewerNode(nullptr); +} + TimeRuler *TimeBasedWidget::ruler() const { return ruler_; diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 562b36a47..9429635c4 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -230,6 +230,8 @@ private slots: void AutoUpdateTimebase(); + void ConnectedNodeRemovedFromGraph(); + }; } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 8cc4c3e1e..b25993dd3 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -261,6 +261,27 @@ ScopePanel *MainWindow::AppendScopePanel() return AppendFloatingPanelInternal(scope_panels_); } +void MainWindow::OpenNodeInViewer(ViewerOutput *node) +{ + if (viewer_panels_.contains(node)) { + // This node already has a viewer, raise it + viewer_panels_.value(node)->raise(); + } else { + // Create a viewer for this node + ViewerPanel* viewer = PanelManager::instance()->CreatePanel(this); + + viewer->SetSignalInsteadOfClose(true); + viewer->setFloating(true); + viewer->setVisible(true); + viewer->ConnectViewerNode(node); + + connect(viewer, &ViewerPanel::CloseRequested, this, &MainWindow::ViewerCloseRequested); + connect(node, &ViewerOutput::RemovedFromGraph, this, &MainWindow::ViewerWithPanelRemovedFromGraph); + + viewer_panels_.insert(node, viewer); + } +} + void MainWindow::SetFullscreen(bool fullscreen) { if (fullscreen) { @@ -481,6 +502,22 @@ void MainWindow::ProjectCloseRequested() Core::instance()->CloseProject(p, true); } +void MainWindow::ViewerCloseRequested() +{ + ViewerPanel* panel = static_cast(sender()); + + viewer_panels_.remove(viewer_panels_.key(panel)); + + panel->deleteLater(); +} + +void MainWindow::ViewerWithPanelRemovedFromGraph() +{ + ViewerOutput* vo = static_cast(sender()); + viewer_panels_.take(vo)->deleteLater(); + disconnect(vo, &ViewerOutput::RemovedFromGraph, this, &MainWindow::ViewerWithPanelRemovedFromGraph); +} + void MainWindow::FloatingPanelCloseRequested() { PanelWidget* panel = static_cast(sender()); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 35db59aee..fbbb38a70 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -71,6 +71,8 @@ public: ScopePanel* AppendScopePanel(); + void OpenNodeInViewer(ViewerOutput* node); + enum ProgressStatus { kProgressNone, kProgressShow, @@ -155,6 +157,7 @@ private: PixelSamplerPanel* pixel_sampler_panel_; QList scope_panels_; NodeTablePanel* table_panel_; + QMap viewer_panels_; #ifdef Q_OS_WINDOWS unsigned int taskbar_btn_id_; @@ -173,6 +176,10 @@ private slots: void ProjectCloseRequested(); + void ViewerCloseRequested(); + + void ViewerWithPanelRemovedFromGraph(); + void FloatingPanelCloseRequested(); void StatusBarDoubleClicked();