From e47c985baaebd297509fe31eee2c26244fa1dd92 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 18 Jun 2020 16:30:36 +1000 Subject: [PATCH 01/21] slider: if a ladder appears, signal the label that the drag has finished --- app/widget/slider/sliderbase.cpp | 2 ++ app/widget/slider/sliderlabel.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index 7818e4fd0..67ff5b7de 100644 --- a/app/widget/slider/sliderbase.cpp +++ b/app/widget/slider/sliderbase.cpp @@ -260,6 +260,8 @@ void SliderBase::LabelDragged() connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &SliderBase::LadderDragged); connect(drag_ladder_, &SliderLadder::Released, this, &SliderBase::LadderReleased); + + label_->CancelDrag(); break; } } diff --git a/app/widget/slider/sliderlabel.h b/app/widget/slider/sliderlabel.h index 081c5eafa..6ac8f9e5a 100644 --- a/app/widget/slider/sliderlabel.h +++ b/app/widget/slider/sliderlabel.h @@ -33,6 +33,11 @@ class SliderLabel : public QLabel public: SliderLabel(QWidget* parent); + void CancelDrag() + { + dragging_ = false; + } + protected: virtual void mousePressEvent(QMouseEvent *ev) override; From 57f858dd92daae08c91b6df07a25f80ee263c497 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 18 Jun 2020 16:30:57 +1000 Subject: [PATCH 02/21] nodetable: correctly update with time --- app/node/value.cpp | 8 ++- app/node/value.h | 11 +++- app/panel/table/table.h | 1 + app/widget/nodetableview/nodetableview.cpp | 56 ++++++++++++++++---- app/widget/nodetableview/nodetableview.h | 3 ++ app/widget/nodetableview/nodetablewidget.cpp | 27 +++++++++- app/widget/nodetableview/nodetablewidget.h | 7 +++ app/window/mainwindow/mainwindow.cpp | 3 ++ 8 files changed, 100 insertions(+), 16 deletions(-) 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/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/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/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index a917143f6..09286c316 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); @@ -518,6 +520,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel() 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); From 115e62f1403b03a326fe98e099ae56cce528a5e5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 18 Jun 2020 18:52:13 +1000 Subject: [PATCH 03/21] slider: further behavior improvements Ladders appears offset on sliders with multipliers. Ladder also appears on initial press (but still shows line edit if mouse wasn't moved) --- app/dialog/richtext/richtext.cpp | 1 - app/widget/colorwheel/colorvalueswidget.cpp | 1 - .../nodeparamviewwidgetbridge.cpp | 4 +- app/widget/slider/sliderbase.cpp | 96 ++++++++++++------- app/widget/slider/sliderbase.h | 17 ++-- app/widget/slider/sliderlabel.cpp | 19 +--- app/widget/slider/sliderlabel.h | 16 ---- app/widget/slider/sliderladder.cpp | 4 + 8 files changed, 73 insertions(+), 85 deletions(-) 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/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/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/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index 67ff5b7de..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,24 +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); - - label_->CancelDrag(); 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 @@ -294,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; } @@ -307,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 6ac8f9e5a..68d0666e3 100644 --- a/app/widget/slider/sliderlabel.h +++ b/app/widget/slider/sliderlabel.h @@ -33,34 +33,18 @@ class SliderLabel : public QLabel public: SliderLabel(QWidget* parent); - void CancelDrag() - { - dragging_ = false; - } - 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..2c8e6128e 100644 --- a/app/widget/slider/sliderladder.cpp +++ b/app/widget/slider/sliderladder.cpp @@ -131,6 +131,10 @@ void SliderLadder::TimerUpdate() QCursor::setPos(drag_start_); #endif + if (!x_mvmt && !y_mvmt) { + return; + } + int y_threshold = fontMetrics().height() / 2; if (qAbs(y_mvmt) > qAbs(x_mvmt) From 0280ade20d9ebb55b84944e5bcd5eb91d202d7a0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 02:23:06 +1000 Subject: [PATCH 04/21] project: improved main window layout loading There were a lot of issues that arose from trying to make GUI changes from another thread (even though we ran those functions in the right thread). Now we store layout information until the end of the load and make the changes then. This works much better from both a business logic and user experience perspective. Also prevents multiple sequences from taking focus during load and starting a render job. --- app/core.cpp | 69 ++++++------- app/project/item/sequence/sequence.cpp | 2 +- app/project/project.cpp | 12 ++- app/project/project.h | 3 +- app/task/project/load/load.cpp | 5 +- app/task/project/load/load.h | 10 +- app/window/mainwindow/CMakeLists.txt | 2 + app/window/mainwindow/mainwindow.cpp | 92 ++++-------------- app/window/mainwindow/mainwindow.h | 9 +- .../mainwindow/mainwindowlayoutinfo.cpp | 96 +++++++++++++++++++ app/window/mainwindow/mainwindowlayoutinfo.h | 52 ++++++++++ 11 files changed, 231 insertions(+), 121 deletions(-) create mode 100644 app/window/mainwindow/mainwindowlayoutinfo.cpp create mode 100644 app/window/mainwindow/mainwindowlayoutinfo.h diff --git a/app/core.cpp b/app/core.cpp index d737a0563..55f0b2c49 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -151,6 +151,30 @@ 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 @@ -480,9 +504,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 +655,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 +1014,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/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/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/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 09286c316..26b765b0d 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -111,43 +111,37 @@ 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, false); + } + + 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()))); + 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()))); + 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_) { @@ -164,11 +158,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) @@ -467,53 +464,6 @@ 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_); 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 From f8a8af9ec3426521a72265d803d40f8260a9614f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 02:26:55 +1000 Subject: [PATCH 05/21] project: focus sequence if there is only one --- app/window/mainwindow/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 26b765b0d..98538cc77 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -118,7 +118,7 @@ void MainWindow::LoadLayout(const MainWindowLayoutInfo &info) } foreach (Sequence* sequence, info.open_sequences()) { - OpenSequence(sequence, false); + OpenSequence(sequence, info.open_sequences().size() == 1); } restoreState(info.state()); From fd8116a9de638c6fd6a4f3d263dca2997e03f018 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 02:36:09 +1000 Subject: [PATCH 06/21] slider: use ctrl to switch axes --- app/widget/slider/sliderladder.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/app/widget/slider/sliderladder.cpp b/app/widget/slider/sliderladder.cpp index 2c8e6128e..5a9c4d235 100644 --- a/app/widget/slider/sliderladder.cpp +++ b/app/widget/slider/sliderladder.cpp @@ -135,14 +135,11 @@ void SliderLadder::TimerUpdate() return; } - int y_threshold = fontMetrics().height() / 2; - - 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) { @@ -165,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()); } } From ab74c1579ef70dd3d1ec054f2b2397fe30d2c10f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 02:44:26 +1000 Subject: [PATCH 07/21] mathnode: temporarily disable identity matrix detection This code was faulty and needs extra functionality elsewhere to actually work correctly. --- app/node/math/math/mathbase.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 From b2a3cede2cdc8aa2fe973cd31471d2aab8904a59 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 17:06:14 +1000 Subject: [PATCH 08/21] renderer: re-use the same opengl instance for all background rendering Previously the OpenGL instance was tied to each render/cache task, creating and destroying it each time one started and stopped. This was completely unnecessary since the instance holds no state and can be shared by all of the render tasks without having to expensively start a new one. --- app/core.cpp | 10 +++++--- app/render/backend/opengl/openglbackend.cpp | 23 +----------------- app/render/backend/opengl/openglbackend.h | 5 ---- app/render/backend/opengl/openglproxy.cpp | 26 +++++++++++++++++++++ app/render/backend/opengl/openglproxy.h | 11 +++++++++ app/render/backend/opengl/openglworker.cpp | 13 +++++------ app/render/backend/opengl/openglworker.h | 5 +--- 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 55f0b2c49..e8f34fcf4 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -181,6 +181,9 @@ void Core::Start() // 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(); @@ -193,9 +196,8 @@ void Core::Start() // Initialize task manager TaskManager::CreateInstance(); - // Load application config - Config::Load(); - + // Initialize OpenGL service + OpenGLProxy::CreateInstance(); // // Start application @@ -223,6 +225,8 @@ void Core::Stop() } } + OpenGLProxy::DestroyInstance(); + MenuShared::DestroyInstance(); TaskManager::DestroyInstance(); 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..c96e52d7a 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 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 From 65ccac1dfede05d784f016dc6b340749dc4d9b74 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 20:25:05 +1000 Subject: [PATCH 09/21] renderer: fixed bug that would render frames even after hash matching Also enables the -Wshadow GCC warning to warn against the code bug that caused this issue (and has caused other issues like it in the past). --- app/CMakeLists.txt | 1 + app/node/input.cpp | 14 +++++++----- app/node/output/track/tracklist.cpp | 6 ++--- app/render/backend/opengl/openglproxy.cpp | 24 ++++++++++---------- app/render/backend/renderbackend.cpp | 6 ++--- app/task/render/render.cpp | 21 +++++++++-------- app/widget/audiomonitor/audiomonitor.cpp | 4 ++-- app/widget/audiomonitor/audiomonitor.h | 2 +- app/widget/menu/menu.cpp | 6 ++--- app/widget/menu/menu.h | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 6 ++--- 11 files changed, 48 insertions(+), 44 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6050fabfe..6f0395705 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/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/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/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index c96e52d7a..08aa8864c 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -276,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: @@ -300,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()); @@ -310,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()); } @@ -354,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(); } @@ -362,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); @@ -370,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/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/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/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/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 908d7e3a2..b89ffe00d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1584,9 +1584,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); } } From a94caa57378f8209e4b25225490231a79df6e1bc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Jun 2020 20:25:49 +1000 Subject: [PATCH 10/21] export: fixed bug that broke audio on export --- app/render/playbackcache.cpp | 1 + app/task/export/export.cpp | 14 +++----------- app/task/export/export.h | 2 -- 3 files changed, 4 insertions(+), 13 deletions(-) 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_; From 5f6e5916bcda5ecab29c40297ae8fc3250472799 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 20 Jun 2020 01:56:41 +1000 Subject: [PATCH 11/21] curvewidget: allow zooming only one axis and allow hiding keyframe tracks --- app/widget/curvewidget/curveview.cpp | 43 ++++++++++++++++++-- app/widget/curvewidget/curveview.h | 4 ++ app/widget/curvewidget/curvewidget.cpp | 19 +++++++-- app/widget/curvewidget/curvewidget.h | 3 ++ app/widget/keyframeview/keyframeviewbase.cpp | 26 ++++++++++++ app/widget/keyframeview/keyframeviewbase.h | 4 ++ 6 files changed, 92 insertions(+), 7 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index a78f97e13..52a7e8eab 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -61,6 +61,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 +126,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); @@ -239,12 +253,33 @@ void CurveView::wheelEvent(QWheelEvent *event) { if (WheelEventIsAZoomEvent(event)) { if (!event->angleDelta().isNull()) { + bool only_vertical = false; + bool only_horizontal = false; + + if (event->modifiers() & Qt::ShiftModifier) { + if (event->modifiers() & Qt::AltModifier) { + only_horizontal = true; + } else { + only_vertical = true; + } + } + if (event->angleDelta().x() + event->angleDelta().y() > 0) { - emit ScaleChanged(GetScale() * 1.1); - SetYScale(GetYScale() * 1.1); + if (!only_vertical) { + emit ScaleChanged(GetScale() * 1.1); + } + + if (!only_horizontal) { + SetYScale(GetYScale() * 1.1); + } } else { - emit ScaleChanged(GetScale() * 0.9); - SetYScale(GetYScale() * 0.9); + if (!only_vertical) { + emit ScaleChanged(GetScale() * 0.9); + } + + if (!only_horizontal) { + SetYScale(GetYScale() *0.9); + } } } } else { diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 2c767acd6..67c2e25c3 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -39,6 +39,8 @@ public: void SetTrackCount(int count); + void SetTrackVisible(int track, bool visible); + public slots: void AddKeyframe(NodeKeyframePtr key); @@ -76,6 +78,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..2453618f9 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); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 21e0400d0..a1c2035c0 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 @@ -87,6 +88,8 @@ private: NodeParamViewKeyframeControl* key_control_; + QList checkboxes_; + private slots: void SelectionChanged(); diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 0bf903b39..257a12e09 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -106,6 +106,11 @@ KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key) item->SetScale(GetScale()); item_map_.insert(key.get(), item); scene()->addItem(item); + + if (hidden_tracks_.contains(key->track())) { + item->setVisible(false); + } + return item; } @@ -306,6 +311,27 @@ void KeyframeViewBase::SetYAxisEnabled(bool e) y_axis_enabled_ = e; } +void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) +{ + 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); + } +} + rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 04e7d6096..705e845c6 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -64,6 +64,8 @@ protected: void SetYAxisEnabled(bool e); + void SetKeyframeTrackVisible(int track, bool visible); + private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); @@ -100,6 +102,8 @@ private: bool currently_autoselecting_; + QList hidden_tracks_; + private slots: void ShowContextMenu(); From e97b5a9a1438fba0cceaaa2f2746a60b1a5bf468 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 21 Jun 2020 13:12:07 +1000 Subject: [PATCH 12/21] nodeparamview: set time on node set Fixes bug where keyframes would land on 0 unless the time is set a second time. --- app/widget/nodeparamview/nodeparamview.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index eaae8d0eb..bc7f1e07d 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -177,18 +177,21 @@ void NodeParamView::SetNodes(QList nodes) if (viewer) { SetTimebase(viewer->video_params().time_base()); + rational time = Timecode::timestamp_to_time(this->GetTimestamp(), timebase()); + // Set viewer as a time target keyframe_view_->SetTimeTarget(viewer); foreach (NodeParamViewItem* item, items_) { item->SetTimeTarget(viewer); + item->SetTime(time); } emit TimeTargetChanged(viewer); } // Forces the scroll to update to this time - keyframe_view_->SetTime(ruler()->GetTime()); + keyframe_view_->SetTime(GetTimestamp()); } } @@ -239,7 +242,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); From 1725f01459b77250ef7100b27d4c7f9ea70024a3 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 22 Jun 2020 00:38:00 +1000 Subject: [PATCH 13/21] paramview: changed to static viewer-bound and fixed various scrolling issues --- app/panel/param/param.cpp | 2 - app/panel/param/param.h | 2 - app/widget/keyframeview/keyframeview.cpp | 9 ++- app/widget/keyframeview/keyframeview.h | 10 +++ app/widget/keyframeview/keyframeviewbase.cpp | 18 +++-- app/widget/nodeparamview/nodeparamview.cpp | 78 +++++++++---------- app/widget/nodeparamview/nodeparamview.h | 15 ++-- app/widget/timebased/timebased.h | 4 +- .../timelinewidget/view/timelineview.cpp | 1 - .../timelinewidget/view/timelineviewbase.cpp | 7 -- .../timelinewidget/view/timelineviewbase.h | 4 - app/window/mainwindow/mainwindow.cpp | 1 + 12 files changed, 81 insertions(+), 70 deletions(-) diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 649998cdf..47d81df49 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -29,7 +29,6 @@ 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); @@ -104,7 +103,6 @@ void ParamPanel::CreateCurvePanel(NodeInput *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(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 0227e45ba..e75350d6b 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); 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 257a12e09..e6c4ad480 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -101,14 +101,18 @@ 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 (hidden_tracks_.contains(key->track())) { - item->setVisible(false); + 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; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index bc7f1e07d..eab4bd815 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -43,18 +43,19 @@ 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); + 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 +74,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 +122,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 +154,6 @@ void NodeParamView::SetNodes(QList nodes) items_.append(item); - QMetaObject::invokeMethod(item, - "SignalAllKeyframes", - Qt::QueuedConnection); - emit OpenedNode(node); if (!found_gizmos && node->HasGizmos()) { @@ -170,28 +162,7 @@ void NodeParamView::SetNodes(QList nodes) } } - ViewerOutput* viewer = nodes_.first()->FindOutputNode(); - - ConnectViewerNode(viewer); - - if (viewer) { - SetTimebase(viewer->video_params().time_base()); - - rational time = Timecode::timestamp_to_time(this->GetTimestamp(), timebase()); - - // Set viewer as a time target - keyframe_view_->SetTimeTarget(viewer); - - foreach (NodeParamViewItem* item, items_) { - item->SetTimeTarget(viewer); - item->SetTime(time); - } - - emit TimeTargetChanged(viewer); - } - - // Forces the scroll to update to this time - keyframe_view_->SetTime(GetTimestamp()); + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); } } @@ -214,6 +185,8 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) TimeBasedWidget::TimebaseChangedEvent(timebase); keyframe_view_->SetTimebase(timebase); + + UpdateItemTime(GetTimestamp()); } void NodeParamView::TimeChangedEvent(const int64_t ×tamp) @@ -225,6 +198,28 @@ 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); + } +} + +void NodeParamView::ConnectNodeInternal(ViewerOutput *n) +{ + SetTimebase(n->video_params().time_base()); +} + +void NodeParamView::DisconnectNodeInternal(ViewerOutput *n) +{ + Q_UNUSED(n) + + SetTimebase(rational()); +} + const QList &NodeParamView::nodes() { return nodes_; @@ -254,11 +249,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..71d4edcfd 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,11 @@ protected: virtual void TimebaseChangedEvent(const rational&) override; virtual void TimeChangedEvent(const int64_t &) override; + virtual void ConnectedNodeChanged(ViewerOutput* n) override; + + virtual void ConnectNodeInternal(ViewerOutput* n) override; + virtual void DisconnectNodeInternal(ViewerOutput* n) override; + private: void UpdateItemTime(const int64_t ×tamp); @@ -77,14 +80,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/timebased/timebased.h b/app/widget/timebased/timebased.h index 49012af1c..bf5b8ac40 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); 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..c044f425e 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -39,7 +39,6 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : playhead_scene_left_(-1), playhead_scene_right_(-1), dragging_playhead_(false), - limit_y_axis_(false), snapped_(false), snap_service_(nullptr) { @@ -271,10 +270,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..cb667dd41 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.h +++ b/app/widget/timelinewidget/view/timelineviewbase.h @@ -73,8 +73,6 @@ protected: bool WheelEventIsAZoomEvent(QWheelEvent* event); - void SetLimitYAxis(bool e); - rational GetPlayheadTime() const; bool PlayheadPress(QMouseEvent* event); @@ -97,8 +95,6 @@ private: QGraphicsScene scene_; - bool limit_y_axis_; - bool snapped_; QList snap_time_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 98538cc77..38f364cb2 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -518,6 +518,7 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel) void MainWindow::TimelineFocused(ViewerOutput* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); + param_panel_->ConnectViewerNode(viewer); Sequence* seq = nullptr; From 6ebcb7162e4b05e79dc7230ce9587c6d64b412c0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 22 Jun 2020 00:39:38 +1000 Subject: [PATCH 14/21] audiovisualwaveform: clamp audio values so they stay inbounds --- app/audio/audiovisualwaveform.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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))); } } } From 1ec0d06e9fe1206d0e8366956933fd97ae009968 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 22 Jun 2020 00:40:19 +1000 Subject: [PATCH 15/21] timebasedpanel: check if node is the same Minor optimization. --- app/panel/timebased/timebased.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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); } From 2207a915a6a239928326551ab43b8d5e04878244 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 23 Jun 2020 05:15:48 +1000 Subject: [PATCH 16/21] viewernode: check if timebase/size have actually changed before signalling Minor optimization. --- app/node/output/viewer/viewer.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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(); } From e681b87988493978419bb0311685e1c098cd9948 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 23 Jun 2020 05:22:58 +1000 Subject: [PATCH 17/21] curve/param/panels: leave curve panel open and have option for timebasedwidget to auto-set timebase --- app/panel/curve/curve.cpp | 5 -- app/panel/curve/curve.h | 2 - app/panel/param/param.cpp | 77 +++++++++----------- app/panel/param/param.h | 9 ++- app/widget/curvewidget/curvewidget.cpp | 11 +-- app/widget/curvewidget/curvewidget.h | 2 + app/widget/nodeparamview/nodeparamview.cpp | 12 --- app/widget/nodeparamview/nodeparamview.h | 3 - app/widget/timebased/timebased.cpp | 25 ++++++- app/widget/timebased/timebased.h | 8 ++ app/widget/timelinewidget/timelinewidget.cpp | 4 +- app/widget/viewer/viewer.cpp | 12 --- 12 files changed, 81 insertions(+), 89 deletions(-) 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 47d81df49..1bd3355d7 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -30,8 +30,6 @@ ParamPanel::ParamPanel(QWidget* parent) : NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::InputDoubleClicked, this, &ParamPanel::CreateCurvePanel); 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); @@ -50,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,50 +89,51 @@ 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()); - connect(view, &NodeParamView::TimebaseChanged, panel, &CurvePanel::SetTimebase); - connect(view, &NodeParamView::TimeChanged, panel, &CurvePanel::SetTimestamp); - 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 e75350d6b..12bc1715e 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -51,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/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 2453618f9..aa294deb2 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -218,8 +218,6 @@ void CurveWidget::ScaleChangedEvent(const double &scale) void CurveWidget::TimeTargetChangedEvent(Node *target) { - ConnectViewerNode(nullptr); - key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); @@ -227,12 +225,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 a1c2035c0..7c33b49c7 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -59,6 +59,8 @@ protected: virtual void TimeTargetChangedEvent(Node* target) override; + virtual void ConnectedNodeChanged(ViewerOutput* n) override; + private: void UpdateInputLabel(); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index eab4bd815..03ce22f18 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -208,18 +208,6 @@ void NodeParamView::ConnectedNodeChanged(ViewerOutput *n) } } -void NodeParamView::ConnectNodeInternal(ViewerOutput *n) -{ - SetTimebase(n->video_params().time_base()); -} - -void NodeParamView::DisconnectNodeInternal(ViewerOutput *n) -{ - Q_UNUSED(n) - - SetTimebase(rational()); -} - const QList &NodeParamView::nodes() { return nodes_; diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 71d4edcfd..9fe697d97 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -64,9 +64,6 @@ protected: virtual void ConnectedNodeChanged(ViewerOutput* n) override; - virtual void ConnectNodeInternal(ViewerOutput* n) override; - virtual void DisconnectNodeInternal(ViewerOutput* n) override; - private: void UpdateItemTime(const int64_t ×tamp); diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index 6c83257d2..b2e15b03b 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_) { diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h index bf5b8ac40..008514d26 100644 --- a/app/widget/timebased/timebased.h +++ b/app/widget/timebased/timebased.h @@ -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/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index b89ffe00d..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() 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); From 8bf653925f3de184bf4a25ba5a503a0bdfb2422c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 23 Jun 2020 05:25:34 +1000 Subject: [PATCH 18/21] curve/timeline: improved user interface behavior * Implements "auto-fit" setting for curve view and sets it on open. * Improves scroll zooming on all TimelineViewBase derivatives. * Improves code sharing for better maintenance. --- app/widget/curvewidget/curveview.cpp | 86 ++++++++++++------- app/widget/curvewidget/curveview.h | 6 ++ app/widget/curvewidget/curvewidget.cpp | 2 + app/widget/keyframeview/keyframeviewbase.cpp | 44 +++------- app/widget/keyframeview/keyframeviewbase.h | 16 +--- app/widget/nodeparamview/nodeparamview.cpp | 4 + app/widget/timebased/timebased.cpp | 4 +- .../timelinewidget/timelinescaledobject.cpp | 17 ++++ .../timelinewidget/timelinescaledobject.h | 6 ++ .../timelinewidget/view/timelineviewbase.cpp | 76 +++++++++++++++- .../timelinewidget/view/timelineviewbase.h | 19 ++++ 11 files changed, 198 insertions(+), 82 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 52a7e8eab..e5fc65cdd 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -20,6 +20,7 @@ #include "curveview.h" +#include #include #include @@ -251,42 +252,22 @@ void CurveView::VerticalScaleChangedEvent(double scale) void CurveView::wheelEvent(QWheelEvent *event) { - if (WheelEventIsAZoomEvent(event)) { - if (!event->angleDelta().isNull()) { - bool only_vertical = false; - bool only_horizontal = false; - - if (event->modifiers() & Qt::ShiftModifier) { - if (event->modifiers() & Qt::AltModifier) { - only_horizontal = true; - } else { - only_vertical = true; - } - } - - if (event->angleDelta().x() + event->angleDelta().y() > 0) { - if (!only_vertical) { - emit ScaleChanged(GetScale() * 1.1); - } - - if (!only_horizontal) { - SetYScale(GetYScale() * 1.1); - } - } else { - if (!only_vertical) { - emit ScaleChanged(GetScale() * 0.9); - } - - if (!only_horizontal) { - 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; @@ -320,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) @@ -402,6 +388,40 @@ 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++) { + min_time = qMin(i.key()->time(), min_time); + max_time = qMax(i.key()->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 67c2e25c3..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); @@ -44,6 +45,8 @@ public: public slots: void AddKeyframe(NodeKeyframePtr key); + void ZoomToFit(); + protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; @@ -55,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); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index aa294deb2..da32088ec 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -169,6 +169,8 @@ void CurveWidget::SetInput(NodeInput *input) } UpdateInputLabel(); + + QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection); } const double &CurveWidget::GetVerticalScale() diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index e6c4ad480..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(); @@ -194,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()); } @@ -257,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(), @@ -288,10 +270,6 @@ void KeyframeViewBase::ScaleChangedEvent(const double &scale) } } -void KeyframeViewBase::VerticalScaleChangedEvent(double) -{ -} - const QMap &KeyframeViewBase::item_map() const { return item_map_; @@ -310,11 +288,6 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target) } } -void KeyframeViewBase::SetYAxisEnabled(bool e) -{ - y_axis_enabled_ = e; -} - void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) { if (!visible == hidden_tracks_.contains(track)) { @@ -336,6 +309,11 @@ void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) } } +void KeyframeViewBase::ContextMenuEvent(Menu& m) +{ + Q_UNUSED(m) +} + rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); @@ -433,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() @@ -480,7 +458,11 @@ void KeyframeViewBase::ShowContextMenu() break; } } + } + ContextMenuEvent(m); + + if (!items.isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); @@ -532,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 705e845c6..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,18 +52,16 @@ 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); @@ -96,10 +92,6 @@ private: QVector selected_keys_; - bool y_axis_enabled_; - - double y_scale_; - bool currently_autoselecting_; QList hidden_tracks_; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 03ce22f18..24f32e7d6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -55,6 +55,8 @@ NodeParamView::NodeParamView(QWidget *parent) : // Set up scroll area layout param_layout_ = new QVBoxLayout(param_widget_area_); param_layout_->setSpacing(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 @@ -162,6 +164,8 @@ void NodeParamView::SetNodes(QList nodes) } } + UpdateItemTime(GetTimestamp()); + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); } } diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index b2e15b03b..5c83206c6 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -482,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/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/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index c044f425e..a5acb99e1 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -40,7 +40,9 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : playhead_scene_right_(-1), dragging_playhead_(false), snapped_(false), - snap_service_(nullptr) + snap_service_(nullptr), + y_axis_enabled_(false), + y_scale_(1.0) { setScene(&scene_); @@ -80,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; @@ -252,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); } } diff --git a/app/widget/timelinewidget/view/timelineviewbase.h b/app/widget/timelinewidget/view/timelineviewbase.h index cb667dd41..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,6 +72,8 @@ protected: virtual void SceneRectUpdateEvent(QRectF&){} + virtual void VerticalScaleChangedEvent(double scale); + bool HandleZoomFromScroll(QWheelEvent* event); bool WheelEventIsAZoomEvent(QWheelEvent* event); @@ -81,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(); @@ -102,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 From d31c373bd0e92e225e445c8059a823cb68bde846 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 24 Jun 2020 01:29:57 +1000 Subject: [PATCH 19/21] curveview: transform times to target for zoom fit --- app/widget/curvewidget/curveview.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index e5fc65cdd..c57072e88 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -404,8 +404,13 @@ void CurveView::ZoomToFit() double max_val = DBL_MIN; for (i=item_map().constBegin(); i!=item_map().constEnd(); i++) { - min_time = qMin(i.key()->time(), min_time); - max_time = qMax(i.key()->time(), max_time); + 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); From b0a5d4f4c3fb8b891e0004aeb8770157e248cece Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 24 Jun 2020 01:36:04 +1000 Subject: [PATCH 20/21] parampanel: set input after timestamp Default TimeBasedWidget behavior is to jump to the playhead, but we want the CurveView to zoom fit on open instead. So we set the input after the timestamp to make this behavior possible. --- app/panel/param/param.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 1bd3355d7..e6731e9b0 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -88,9 +88,9 @@ void ParamPanel::CreateCurvePanel(NodeInput *input) panel = Core::instance()->main_window()->AppendCurvePanel(); - panel->SetInput(input); panel->ConnectViewerNode(view->GetConnectedNode()); panel->SetTimestamp(view->GetTimestamp()); + panel->SetInput(input); connect(view, &NodeParamView::TimeChanged, this, &ParamPanel::ParamViewTimeChanged); connect(panel, &CurvePanel::TimeChanged, this, &ParamPanel::CurvePanelTimeChanged); From e064d3798848c89c641a11114ccc5856eecca5aa Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 24 Jun 2020 02:14:51 +1000 Subject: [PATCH 21/21] mainwindow: check for connected items before saving Fixes segfault when saving an empty project/a project with no sequence open. --- app/window/mainwindow/mainwindow.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 38f364cb2..1c5f2b111 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -129,11 +129,15 @@ MainWindowLayoutInfo MainWindow::SaveLayout() const MainWindowLayoutInfo info; foreach (ProjectPanel* panel, folder_panels_) { - info.add_folder(static_cast(panel->get_root_index().internalPointer())); + if (panel->project()) { + info.add_folder(static_cast(panel->get_root_index().internalPointer())); + } } foreach (TimelinePanel* panel, timeline_panels_) { - info.add_sequence(static_cast(panel->GetConnectedViewer()->parent())); + if (panel->GetConnectedViewer()) { + info.add_sequence(static_cast(panel->GetConnectedViewer()->parent())); + } } info.set_state(saveState());